2010-11-17 6 views
3

Для XMPP interface for the Stack Overflow chat Я разбираю канал JSON из чата и генерирую объекты Ruby для каждого события чата, такие как отправленные сообщения, отправленные изменения, вход в систему или выход пользователей и т. Д. Я также генерирую события для «косаемых команд», отправленных сервер XMPP, например, «/ help» или «/ auth», чтобы позволить пользователю XMPP проходить аутентификацию со своей учетной записью чата переполнения стека.Как я могу улучшить иерархию классов событий?

Я создал эти классы в иерархии я чувствую делает хороший логический смысл:

class SOChatEvent # base class 
| 
|--- class SOXMPPEvent # base for all events that are initiated via XMPP 
| | 
| |--- class SOXMPPMessage # messages sent to the XMPP bridge via XMPP 
| | | 
| | |--- class SOXMPPMessageToRoom # messages sent from an XMPP user to an XMPP MUC 
| | | 
| | |--- class SOXMPPUserCommand # class for "slash commands", that is, messages starting 
| | | |       # with /, used for sending commands to the bridge 
| | | | 
| | | |--- class SOXMPPUserHelpCommand 
| | | |--- class SOXMPPUserLoginCommand 
| | | |--- class SOXMPPUserBroadcastCommand 
| 
|--- class SOChatRoomEvent # base class for all events that originate from an SO chat room 
| | 
| |--- class SOChatMessage # messages sent to an SO chat room via the SO chat system 
| | | 
| | |--- class SOChatMessageEdit # edits made to a prior SOChatMessage 
| | 
| |--- class SOChatUserEvent # events related to SO chat users 
| | | 
| | |--- class SOChatUserJoinRoom #Event for when a So user joins a room 
| | |--- class SOChatUserLeaveRoom #Event for when a So user leaves a room 

(etc) 

Вы можете увидеть полную иерархию и источник in Trac или via SVN.

Мой вопрос двоякий: во-первых, каков наилучший способ создания этих событий? То, что я сейчас делаю это разбор JSON события с помощью гигантского switch о --well, это рубин, так что это case заявление - и это не гигантский еще, но это будет, если я продолжу этот путь:

rooms.each do |room| 
    rid = "r"+"#{room.room_id}" 
    if !data[rid].nil? 
    @last_update = data[rid]['t'] if data[rid]['t'] 

    if data[rid]["e"] 
     data[rid]["e"].each do |e| 
     puts "DEBUG: found an event: #{e.inspect}" 
     case e["event_type"] 
      when 1 
      event = SOChatMessage.new(room,e['user_name']) 
      event.encoded_body = e['content'] 
      event.server = @server 
      events.push event 
      when 2 
      event = SOChatMessageEdit.new(room,e['user_name']) 
      event.encoded_body = e['content'] 
      event.server = @server 
      events.push event 
      when 3 
      user = SOChatUser.new(e['user_id'], e['user_name']) 
      event = SOChatUserJoinRoom.new(room,user) 
      event.server = @server 
      events.push event 
      when 4 
      user = SOChatUser.new(e['user_id'], e['user_name']) 
      event = SOChatUserLeaveRoom.new(room,user) 
      event.server = @server 
      events.push event 
     end 
     end 
    end 
    end 
end 

Но я полагаю, что должен быть лучший способ справиться с этим! Что-то вроде SOChatEvent.createFromJSON(json_data) ... Но каков наилучший способ структурирования моего кода, чтобы объекты соответствующего подкласса создавались в ответ на заданный event_type?

Во-вторых, я пока не использую подклассы муравьев SOXMPPUserCommand. Сейчас все команды - это всего лишь экземпляры SOXMPPUserCommand, и этот класс имеет один метод execute, который переключается на основе регулярного выражения команды. Много та же проблема - я знаю, что есть лучший способ, я просто не уверен, что лучший способ:

def handle_message(msg) 
    puts "Room \"#{@name}\" handling message: #{msg}" 
    puts "message: from #{msg.from} type #{msg.type} to #{msg.to}: #{msg.body.inspect}" 

    event = nil 

    if msg.body =~ /\/.*/ 
     #puts "DEBUG: Creating a new SOXMPPUserCommand" 
     event = SOXMPPUserCommand.new(msg) 
    else 
     #puts "DEBUG: Creating a new SOXMPPMessageToRoom" 
     event = SOXMPPMessageToRoom.new(msg) 
    end 

    if !event.nil? 
     event.user = get_soxmpp_user_by_jid event.from 
     handle_event event 
    end 
    end 

и:

class SOXMPPUserCommand < SOXMPPMessage 
    def execute 
    case @body 
     when "/help" 
     "Available topics are: help auth /fkey /cookie\n\nFor information on a topic, send: /help <topic>" 
     when "/help auth" 
     "To use this system, you must send your StackOverflow chat cookie and fkey to the system. To do this, use the /fkey and /cookie commands" 
     when "/help /fkey" 
     "Usage: /fkey <fkey>. Displays or sets your fkey, used for authentication. Send '/fkey' alone to display your current fkey, send '/fkey <something>' to set your fkey to <something>. You can obtain your fkey via the URL: javascript:alert(fkey().fkey)" 
     when "/help /cookie" 
     "Usage: /cookie <cookie>. Displays or sets your cookie, used for authentication. Send '/cookie' alone to display your current fkey, send '/cookie <something>' to set your cookie to <something>" 
     when /\/fkey(.*)?/ 
     if $1.nil? 
      "Your fkey is \"#{@user.fkey}\"" 
     else 
      @user.fkey = $1.strip 
      if @user.authenticated? 
      "fkey set to \"#{@user.fkey}\". You are now logged in and can send messages to the chat" 
      else 
      "fkey set to \"#{@user.fkey}\". You must also send your cookie with /cookie before you can chat" 
      end 
     end 
     when /\/cookie(.*)?/ 
     if $1.nil? 
      "Your cookie is: \"#{@user.cookie}\"" 
     else 
      if $1 == " chocolate chip" 
      "You get a chocolate chip cookie!" 
      else 
      @user.cookie = $1.strip 
      if @user.authenticated? 
       "cookie set to \"#{@user.cookie}\". You are now logged in and can send messages to the chat" 
      else 
       "cookie set to \"#{@user.cookie}\". You must also send your fkey with /fkey before you can chat" 
      end 
      end 
     end 
     else 
     "Unknown Command \"#{@body}\"" 
    end 
    end 
end 

Я знаю, что есть лучший способ сделать это, просто не уверен, что конкретно это такое. Должна ли ответственность за создание подклассов SOXMPPUserCommand упасть на SOXMPPUserCommand? Должны ли все подклассы регистрироваться у родителя? Нужен ли мне новый класс?

Каков наилучший способ создания объектов подклассов в такой иерархической структуре?

+0

C'mon люди! Не позволяйте рубину отпугнуть вас! Это должно относиться практически к любому языку ООП! – Josh

ответ

2

Адрес вашего первого вопроса. Вот некоторые идеи, которые вы хотели бы рассмотреть.

Сначала структурируйте свои подклассы, чтобы все они использовали одни и те же параметры инициализации. Кроме того, вы могли бы поставить некоторые другие инициирующего кода там же (например, ваши encoded_body и сервера аксессорах Вот скелет того, что я имею в виду:.

# SOChat Class skeleton structure 
class SOChatSubClass #< inherit from whatever parent class is appropriate 
    attr_accessor :encoded_body, :server, :from, :to, :body 

    def initialize(event, room, server) 
    @encoded_body = event['content'] 
    @server = server 
    SOChatEvent.events.push event 

    #class specific code 
    xmpp_message = event['message'] 
    @from = xmpp_message.from 
    @to = xmpp_message.to 
    @body = xmpp_message.body 
    #use super to call parent class initialization methods and to DRY up your code 
    end 
end 

Обратите внимание, что в моем примере вы еще дублируется код в подклассах. В идеале вы бы вытащили дублирование, поместив его в соответствующий родительский класс.

Если у вас возникли проблемы с созданием общего списка параметров инициализации, вместо этого передайте список аргументов (событие , room, server), измените классы, чтобы принять список аргументов как hash {: event => event,: room => room,: server => server и т. д.}.

Независимо от того, как только у вас есть общая структура параметров для инициализации классов, вы можете инициализировать их немного более динамично, устраняя необходимость в операторе case.

class SOChatEvent 
    class << self; attr_accessor :events; end 
    @events = [] 

     @@event_parser = { 
           0 => SOChatSubClass, #hypothetical example for testing 
           1 => SOChatMessage, 
           2 => SOChatMessageEdit, 
           #etc 
           } 
    def self.create_from_evt(json_event_data, room=nil, server=nil) 
     event_type = json_event_data["event_type"] 
     event_class = @@event_parser[event_type] 
     #this creates the class defined by class returned in the @@event_parser hash 
     event_obj = event_class.new(json_event_data, room, server) 
    end 

    #rest of class 
end 

@@event_parser содержит отображение между типом события и классом для выполнения этого типа события. Вы просто назначаете соответствующий класс переменной и относитесь к ней так же, как к фактическому классу.

код как следующий будет создать объект соответствующего класса:

event_obj = SOChatEvent.create_from_evt(json_event_data, 
             "some room", 
             "some server") 

Примечание: Существуют дополнительные оптимизации, которые можно было бы сделать, что я при условии, чтобы быть еще более чистым и более кратким, но, надеюсь, что это помогает вы попадаете за горб дела.

Edit: Я забыл упомянуть экземпляр класса переменной SOChatEvent.events созданный с этим: class << self; attr_accessor :events; end @events = []

Вы толкали события стека событий, но я не ясно, где вы хотите, что стек существует и является ли она был глобальным списком событий или специфичным для определенного класса. Тот, который я сделал, является глобальным, поэтому не стесняйтесь изменять его, если вы хотите, чтобы стеки событий были ограничены определенными классами или экземплярами.

+0

* Awecome *, Спасибо @forforf! Этот вопрос не получал любви :-) Ваш код намного чище. События «стек» на самом деле представляют собой просто массив, который имеет функции вытягивания событий для каждой комнаты. Он не должен быть глобальным – Josh

+0

[Читай источник для SOChatEventCollection в Trac] (http://trac.digitalfruition.com/soxmpp/browser/trunk/classes/SOChatEventCollection.rb). В основном он хранит события во внутренней структуре следующим образом: '@my_events_by_server [server] [room.room_id] .push (event)', а затем позволяет мне просто переносить события для одной комнаты. – Josh

+0

А, я вижу. Тогда просто проигнорируйте эту часть и придерживайтесь того, что у вас есть. – forforf