2009-03-03 17 views
16

Vengo de un fondo de C#, y acabo de comenzar a programar en Ruby. La cuestión es que necesito saber cómo puedo organizar eventos en mis clases para que varios observadores puedan activarse cuando las cosas sucedan.¿Cómo hacer eventos en Ruby?

El problema es que los libros que tengo en Ruby ni siquiera mencionan eventos, y mucho menos dan ejemplos. ¿Alguien es capaz de ayudarme?

Respuesta

4

Intenté escribir una biblioteca de GUI en Ruby con un poco de C y principalmente Ruby. Terminó siendo tan lento que me rendí y nunca lo lancé. Pero escribí un sistema de eventos para él que intenté hacer más fácil que C# 's. Lo reescribí un par de veces para que sea más fácil de usar. Espero que sea de alguna ayuda.

class EventHandlerArray < Array 
    def add_handler(code=nil, &block) 
    if(code) 
     push(code) 
    else 
     push(block) 
    end 
    end 
    def add 
    raise "error" 
    end 
    def remove_handler(code) 
    delete(code) 
    end 
    def fire(e) 
    reverse_each { |handler| handler.call(e) } 
    end 
end 

# with this, you can do: 
# event.add_handler 
# event.remove_handler 
# event.fire (usually never used) 
# fire_event 
# when_event 
# You just need to call the events method and call super to initialize the events: 
# class MyControl 
# events :mouse_down, :mouse_up, 
#   :mouse_enter, :mouse_leave 
# def initialize 
#  super 
# end 
# def when_mouse_up(e) 
#  # do something 
# end 
# end 
# control = MyControl.new 
# control.mouse_down.add_handler { 
# puts "Mouse down" 
# } 
# As you can see, you can redefine when_event in a class to handle the event. 
# The handlers are called first, and then the when_event method if a handler didn't 
# set e.handled to true. If you need when_event to be called before the handlers, 
# override fire_event and call when_event before event.fire. This is what painting 
# does, for handlers should paint after the control. 
# class SubControl < MyControl 
# def when_mouse_down(e) 
#  super 
#  # do something 
# end 
# end 
def events(*symbols) 
    # NOTE: Module#method_added 

    # create a module and 'include' it 
    modName = name+"Events" 
    initStr = Array.new 
    readerStr = Array.new 
    methodsStr = Array.new 
    symbols.each { |sym| 
    name = sym.to_s 
    initStr << %Q{ 
     @#{name} = EventHandlerArray.new 
    } 
    readerStr << ":#{name}" 
    methodsStr << %Q{ 
     def fire_#{name}(e) 
     @#{name}.fire(e) 
     when_#{name}(e) if(!e.handled?) 
     end 
     def when_#{name}(e) 
     end 
    } 
    } 
    eval %Q{ 
    module #{modName} 
     def initialize(*args) 
     begin 
      super(*args) 
     rescue NoMethodError; end 
     #{initStr.join} 
     end 
     #{"attr_reader "+readerStr.join(', ')} 
     #{methodsStr.join} 
    end 
    include #{modName} 
    } 
end 

class Event 
    attr_writer :handled 
    def initialize(sender) 
    @sender = @sender 
    @handled = false 
    end 
    def handled?; @handled; end 
end 
-1

no estoy seguro de exactamente lo que quieres decir, pero que probablemente podría utilizar excepciones en sus clases y criarlos en ciertos "eventos". Si necesita un evento para el desarrollo de la GUI, la mayoría de los marcos de GUI definen su propio estilo de manejo de eventos.

Espero que esto responda algo a su pregunta.

+0

quiero utilizar el patrón de diseño Observer. – Ash

21

La pregunta ya ha sido respondida, pero hay un observer integrado en la biblioteca estándar si desea darle un vistazo. Lo he usado en el pasado para un proyecto de juego pequeño, y funciona muy bien.

3

Muy simple Ruby oyente. Esto no es exactamente un reemplazo para los eventos .NET, pero este es un ejemplo extremadamente simple de un oyente muy simple.

module Listenable 

    def listeners() @listeners ||= [] end 

    def add_listener(listener) 
     listeners << listener 
    end 

    def remove_listener(listener) 
     listeners.delete listener 
    end 

    def notify_listeners(event_name, *args) 
     listeners.each do |listener| 
      if listener.respond_to? event_name 
       listener.__send__ event_name, *args 
      end 
     end 
    end 

end 

de usar:

class CowListenable 
    include Listenable 

    def speak 
     notify_listeners :spoken, 'moooo!' 
    end 

end 

class CowListener 

    def initialize(cow_listenable) 
     cow_listenable.add_listener self 
    end 

    def spoken(message) 
     puts "The cow said '#{message}'" 
    end 

end 

cow_listenable = CowListenable.new 
CowListener.new(cow_listenable) 
cow_listenable.speak 

Salida:

The cow said 'moooo!' 
+1

Esta respuesta se siente más ruby-ish. Se basa en la naturaleza dinámica del lenguaje en lugar de emular uno fuertemente tipado, es fácil de usar y comprender, y es compatible con multidifusión. –

1

Revelación: Yo soy el mantenedor de la event_aggregator joya

Dependiendo de cómo desea acercarse a la problema que podría utilizar un evento aggreg ator. De esta forma puede publicar mensajes de un tipo determinado y luego hacer que sus objetos escuchen los tipos que desea que reciban. En ciertos casos, esto puede ser mejor que los eventos normales porque se obtiene un acoplamiento muy flexible entre los objetos. El productor y el oyente del evento no necesitan compartir una referencia con el otro.

Hay una joya que te ayuda con esto llamado event_aggregator. Con él se pueden realizar las siguientes acciones:

#!/usr/bin/ruby 

require "rubygems" 
require "event_aggregator" 

class Foo 
    include EventAggregator::Listener 
    def initialize() 
     message_type_register("MessageType1", lambda{|data| puts data }) 

     message_type_register("MessageType2", method(:handle_message)) 
    end 

    def handle_message(data) 
     puts data 
    end 

    def foo_unregister(*args) 
     message_type_unregister(*args) 
    end 
end 

class Bar 
    def cause_event 
     EventAggregator::Message.new("MessageType1", ["Some Stuff",2,3]).publish 
    end 
    def cause_another_event 
     EventAggregator::Message.new("MessageType2", ["Some More Stuff",2,3]).publish 
    end 
end 

f = Foo.new 

b = Bar.new 
b.cause_event 
b.cause_another_event 
# => Some Stuff 
    2 
    3 
# => Some More Stuff 
    2 
    3 

ser consciente de que es asíncrono de forma predeterminada, por lo que si se ejecuta este script solo el guión podría salir antes de que se pasan los acontecimientos. Para deshabilitar el uso asíncrono comportamiento:

EventAggregator::Message.new("MessageType1", ["Some Stuff",2,3], false).publish 
#The third parameter indicates async 

Esperemos que esto puede ser útil en su caso

Cuestiones relacionadas