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

标签 oop events class class-hierarchy

对于XMPP interface for the Stack Overflow chat我正在从聊天中解析 JSON 提要并为每个聊天事件生成 Ruby 对象,例如发送的消息、发送的编辑、用户登录或注销等。我还为发送到 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 本身身上吗?所有的子类都应该向父类注册吗?我需要新类(class)吗?

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

最佳答案

解决您的第一个问题。以下是您可能会考虑的一些想法

首先,构造子类,使它们都使用相同的启动参数。此外,您还可以在那里放置一些其他启动代码(例如您的 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 

请注意,在我的示例中,子类中仍然会有重复的代码。理想情况下,您可以通过将重复项放入适当的父类中来提取重复项。

如果您在创建启动参数的公共(public)列表时遇到问题,那么与其传入参数列表(事件、房间、服务器),不如更改类以接受参数列表作为散列 {:event => 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")

注意:可以对我提供的内容进行进一步的优化,以使其更加清晰和简洁,但希望这能帮助您克服 case 语句的障碍。

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

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

关于oop - 如何改进事件类的层次结构?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/4206927/

相关文章:

php - 一种非OO的PHP设计模式,最适合这种情况的PHP模式

java - 有没有办法在 Java 中将 boolean 类型指定为泛型?

cocoa - 在 Cocoa 应用程序 WebView 中捕获键盘事件

python - 使用 ADT 方法转换程序 python

c++ - 使用 {a,b,c} 作为参数的构造函数或 {a,b,c} 实际上在做什么?

class - 为什么我可以将方法标记为隐式而不是构造函数?

php - php __set()、__get和简单设置、获取函数的区别

c++ - 虚构造函数以及在其中调用虚函数会发生什么

javascript - 窗口选项卡按下事件

javascript - JavaScript 可以监听网页中嵌入的 QuickTime 对象的状态变化吗?