代码之家  ›  专栏  ›  技术社区  ›  Josh Andreas Rehm

如何改进事件类的层次结构?

  •  3
  • Josh Andreas Rehm  · 技术社区  · 14 年前

    对于 XMPP interface for the Stack Overflow chat 我正在分析来自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 .

    我的问题有两个:首先,实例化这些事件的最佳方法是什么?我目前正在做的是使用一个巨大的 switch 声明——嗯,是ruby所以是 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 方法,该方法基于命令的regex进行切换。同样的问题——我知道有更好的办法,我只是不知道最好的办法是什么:

    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命令 它自己?所有子类都应该向父类注册吗?我需要一个新班吗?

    在这种层次结构中实例化子类对象的最佳方法是什么?

    1 回复  |  直到 7 年前
        1
  •  2
  •   forforf    14 年前

    回答你的第一个问题。以下是一些你可能想考虑的想法

    首先,构造子类,使它们都使用相同的初始化参数。此外,您还可以将其他一些启动代码(例如您的编码体和服务器访问器)放在那里。我的意思是:

    # 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 
    

    注意,在我的示例中,子类中仍然有重复的代码。理想情况下,您可以通过将副本放在适当的父类中来消除它。

    如果在创建启动参数的公共列表时遇到问题,请更改类以接受参数列表作为哈希值{:event=>event,:room=>room,:server=>server,etc},而不是传入参数列表(event、room、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")
    

    注意:有进一步的优化可以做什么,我提供了更干净和更简洁,但希望这有助于你克服驼峰的情况说明。

    编辑:我忘了提到类实例变量 SOChatEvent.events 使用此创建: class << self; attr_accessor :events; end @events = []

    您将事件推到事件堆栈上,但我不清楚您希望栈存在于哪里,它是否是全局事件列表,或者特定于特定的类。我所做的是全局的,所以如果您希望将事件堆栈约束到某些类或实例,可以随意更改它。