Pergunta

Para o Interface XMPP para o bate -papo de transbordamento de pilha Estou analisando o feed json do bate-papo e gerando objetos de rubi para todos os eventos de bate-papo, como mensagens enviadas, edições enviadas, usuários efetuando login ou fora, etc. Eu também gero eventos para "comando de barra" enviados ao servidor XMPP, como "/ajuda" ou "/auth" para permitir que o usuário do XMPP se autentique com sua conta de bate -papo do Stack Overflow.

Eu configurei essas aulas em uma hierarquia que sinto faz um bom sentido lógico:

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)

Você pode ver a hierarquia e a fonte completas em Trac ou via svn.

Minha pergunta é dupla: primeiro, qual é a melhor maneira de instanciar esses eventos? O que estou fazendo atualmente é analisar os eventos JSON usando um gigante switch declaração -bem, é rubi, então é um case declaração - e, não é gigante ainda, mas será se eu continuar desta maneira:

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

Mas imagino que tenha que haver uma maneira melhor de lidar com isso! Algo como SOChatEvent.createFromJSON( json_data )... Mas, qual é a melhor maneira de estruturar meu código para que os objetos da subclasse adequados sejam criados em resposta a um determinado event_type?

Segundo, na verdade não estou usando subclasses de formigas de SOXMPPUserCommand ainda. No momento, todos os comandos são apenas instâncias de SOXMPPUserCommand por si só, e essa classe tem um único execute Método que alterna com base no regex do comando. Quase o mesmo problema - eu sei que há uma maneira melhor, não tenho certeza de qual é a melhor maneira:

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

e:

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

Eu sei que há uma maneira melhor de fazer isso, mas não tenho certeza do que é especificamente. A responsabilidade de criar subclasses de SOXMPPUserCommand cair sobre SOXMPPUserCommand em si? Todas as subclasses devem se registrar com o pai? Eu preciso de uma nova aula?

Qual é a melhor maneira de instanciar objetos de subclasses em uma estrutura tão hierárquica?

Foi útil?

Solução

Abordando sua primeira pergunta. Aqui estão algumas idéias que você gostaria de considerar

Primeiro, estrutura você subclasses para que todos usem os mesmos parâmetros de iniciação. Além disso, você também pode colocar alguns dos outros códigos iniciantes (como os acessadores codificados e de servidores. Aqui está um esqueleto do que quero dizer:

# 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 

Observe que, no meu exemplo, você ainda terá código duplicado nas subclasses. Idealmente, você retiraria a duplicação colocando -a na classe pai apropriada.

Se você tiver problemas para criar uma lista comum de parâmetros de iniciação, em vez de passar em uma lista de argumentos (evento, quarto, servidor), altere as classes para aceitar uma lista de argumentos como um hash {: event => event ,: Room = > sala ,: servidor => servidor, etc}.

Independentemente disso, depois de ter uma estrutura de parâmetros comum para inicializar as classes, você pode inicializá -las um pouco mais dinamicamente, eliminando a necessidade da instrução 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 Contém o mapeamento entre o tipo de evento e a classe para implementar esse tipo de evento. Você apenas atribui a classe apropriada a uma variável e a trata exatamente como a classe real.

Código como o seguinte criaria um objeto da classe apropriada:

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

Nota: Existem outras otimizações que podem ser feitas no que eu forneci para ser ainda mais limpo e mais conciso, mas espero que isso ajude você a superar a hump da declaração do caso.

Edit: esqueci de mencionar a variável de instância da classe SOChatEvent.events criado com isso:class << self; attr_accessor :events; end @events = []

Você estava empurrando eventos para uma pilha de eventos, mas eu não estava claro onde você queria que essa pilha existisse e se era uma lista de eventos globais ou específica para uma classe específica. O que eu fiz é global, então sinta -se à vontade para alterá -lo se desejar a pilha de eventos restringida a determinadas classes ou instâncias.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top