【问题标题】:How to do Events in Ruby?如何在 Ruby 中进行事件处理?
【发布时间】:2010-10-10 22:46:10
【问题描述】:

我来自 C# 背景,刚刚开始使用 Ruby 进行编程。问题是,我需要知道如何在我的类中引发事件,以便在需要发生事情时触发各种观察者。

问题是我所拥有的关于 Ruby 的书籍甚至都没有提及事件,更不用说提供示例了。有人可以帮助我吗?

【问题讨论】:

    标签: ruby design-patterns events observer-pattern


    【解决方案1】:

    问题已经得到解答,但是如果您想看一下,标准库中内置了一个observer。我以前在一个小游戏项目中使用过,效果很好。

    【讨论】:

      【解决方案2】:

      我尝试用一​​点 C 语言(主要是 Ruby)在 Ruby 中编写一个 GUI 库。它最终变得如此缓慢,我放弃了并且从未发布它。但是我为它编写了一个事件系统,我试图使它比 C# 更容易。我重写了几次以使其更易于使用。希望对大家有所帮助。

      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
      

      【讨论】:

        【解决方案3】:

        非常简单的 Ruby 监听器。这并不能完全替代 .NET 事件,但这是一个非常简单的监听器示例。

        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
        

        使用方法:

        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
        

        输出:

        The cow said 'moooo!'
        

        【讨论】:

        • 这个答案感觉最红宝石。它依赖于语言的动态特性,而不是模拟强类型语言,易于使用和理解,并且支持多播。
        【解决方案4】:

        披露:我是event_aggregator gem 的维护者

        根据您想要解决问题的方式,您可能会使用事件聚合器。通过这种方式,您可以发布某种类型的消息,然后让您的对象监听您希望它们接收的类型。在某些情况下,这可能比正常事件更好,因为您的对象之间的耦合非常松散。事件生产者和监听者不需要共享对对方的引用。

        有一个叫做event_aggregator的宝石可以帮助你。有了它,您可以执行以下操作:

        #!/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
        

        请注意,默认情况下它是异步的,因此如果您只执行此脚本,则脚本可能会在事件传递之前退出。要禁用异步行为,请使用:

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

        希望这对您的情况有所帮助

        【讨论】:

          【解决方案5】:

          我不确定你的意思,但你可能会在你的类中使用异常并在某些“事件”上引发它们。如果您需要用于 GUI 开发的事件,那么大多数 GUI 框架都会定义自己的事件处理风格。

          希望这能回答您的问题。

          【讨论】:

          • 我想使用观察者设计模式。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2011-06-27
          • 1970-01-01
          • 1970-01-01
          • 2016-04-11
          • 1970-01-01
          • 2011-08-06
          • 2010-11-14
          相关资源
          最近更新 更多