Como posso melhorar a hierarquia das minhas aulas de eventos?
-
25-09-2019 - |
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?
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.