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

时间:2010-11-17 16:51:57

标签: oop events class class-hierarchy

对于XMPP interface for the Stack Overflow chat我正在从聊天中解析JSON提要并为每个聊天事件生成Ruby对象,例如发送的消息,发送的编辑,登录或注销的用户等。我还生成“斜杠”的事件-commands“发送到XMPP服务器,如”/ help“或”/ auth“,以允许XMPP用户使用其Stack Overflow聊天帐户进行身份验证。

我已经在层次结构中设置了这些类,我觉得它具有良好的逻辑意义:

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 Tracvia SVN

我的问题有两个:首先,实例化这些事件的最佳方法是什么?我目前正在做的是使用一个巨大的switch语句解析JSON事件 - 好吧,它是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的ant子类。现在所有命令都只是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本身吗?是否所有子类都应与父级注册?我需要一个新班级吗?

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

1 个答案:

答案 0 :(得分: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 

请注意,在我的示例中,您仍然会在子类中包含重复的代码。理想情况下,您可以通过将其置于适当的父类中来实现复制。

如果您在创建公共参数列表时遇到问题,那么不要传入参数列表(事件,房间,服务器),而是更改类以接受参数列表作为哈希{:event =&gt; event,:room =&gt; room,:server =&gt;服务器等}。

无论如何,一旦你有一个用于初始化类的公共参数结构,你可以更动态地初始化它们,从而消除了对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.eventsclass << self; attr_accessor :events; end @events = []

您正在将事件推送到事件堆栈,但我不清楚您希望该堆栈存在的位置以及它是全局事件列表还是特定于特定类。我做的那个是全局的,所以如果你想要将事件堆栈限制在某些类或实例中,请随意更改它。