【问题标题】:How do I attach an observer to a method without adding code to the method itself?如何在不向方法本身添加代码的情况下将观察者附加到方法?
【发布时间】:2013-08-28 16:00:28
【问题描述】:

我正在尝试通过创建一个新的 Listener 类来让一个方法触发另一个方法。我真的很想简化代码,并且不涉及添加任何特定于回调方法或触发方法的内容。基本上,我想做的是:

def level_up
  level += 1
end

def print_level
  puts "Level Up! (#{level})"
end

notify_level = Listener.new(:level_up, :print_level);

我的 Listener 类(现在)是这样的:

# Listener.new(attached_to, callbacks)
class Listener
  def initialize(attached_to, function)
    @owner, @callback = attached_to, function
  end

  def owner
    @owner
  end

  def callback
    @callback
  end

  def trigger
    # execute callback manually
    self.method(@owner).call
    self.method(@callback).call
  end
end

为了调用两者,我需要自己执行notify_level.trigger,但我想要的是执行level_up并调用print_level。我知道有人会提到一些关于观察者的事情,但我需要的不仅仅是这些。我想坚持干。为每个方法手动添加观察者和侦听器真是太糟糕了,尤其是因为我不能轻松地添加或删除它们。

【问题讨论】:

    标签: ruby observer-pattern


    【解决方案1】:

    我个人不是这种模式的忠实粉丝,但这是一个有趣的问题,所以这是我的解决方案。应该在 Ruby 1.9 及更高版本中工作。

    module MethodListener
      @@observed_methods = {}
    
      def method_added(method)
        alias_name = "__#{method}_orig"
        return if method_defined?(alias_name) || method.match(/__.*_orig/)
    
        alias_method alias_name, method
        define_method(method) do |*args|
          ret = send(alias_name, *args)
          (@@observed_methods[method] || []).each {|callback| send(callback)}
          ret
        end
      end
    
      def listen(owner, callback)
        (@@observed_methods[owner] ||= []) << callback
      end
    end
    

    使用示例:

    class A
      extend MethodListener
    
      def b(a,b)
        puts "b #{a} #{b}"
        true
      end
    
      def c
        puts 'c'
      end
    
      listen :b, :c
    end
    
    A.new.b(1,2) # => true
    # Prints:
    # b 1 2
    # c
    

    【讨论】:

    • 感谢您的解决方案,但我已经想出了自己的解决方案。一种也可以将参数传递给回调的方法。我会发布它,以便您可以查看。
    • 嗯。好吧,我不能假装我的解决方案是最好的主意(它使实例方法的数量增加了一倍),但应该很容易扩展它以允许将 args 传递给回调或运行 proc。我认为它优于您的替代方案的优势在于它将始终执行回调而无需生成对象。既然您必须生成一个新的Event 实例或将其存储以在将来调用trigger,为什么不定义一个新方法,首先调用infinite_loop_b,然后调用infinite_loop_a
    • 这实际上只是在验证我的代码结构。我正在以特定的方式进行操作。可以这样想:玩家 Xyxx 装备了燃烧之剑,每当它击中对手时会造成 2 点火焰伤害。我想在玩家装备剑时创建一个新事件,然后在我解除装备时销毁它。老实说,我对你的回答犹豫不决的原因是因为你使用了像 alias_name = "__#{method}_orig"return if method_defined?(alias_name) || method.match(/__.*_orig/) 这样的东西,它们看起来运行缓慢。
    • 当然可以使用您最熟悉的任何解决方案 :) 条件和别名仅在将方法添加到类时进行评估,因此除非您在运行时向类动态添加方法,否则它将仅在启动时生效。
    【解决方案2】:

    我将原始代码更改为更具语义性,因此更有意义。

    class Event
      def initialize(event, callback_array = [])
        if callback_array.kind_of? Array
          @callbacks = callback_array
        else
          @callbacks = [callback_array]
        end
    
        @event = event
      end
    
      def trigger(*args)
    
        self.method(@event).call *args
    
        @callbacks.each{ |callback|
          if callback.instance_of? Event
            callback.trigger *args
          else
            method(callback).call *args
          end
        }
      end
    
      def add(callback)
        @callbacks.push callback
      end
    
      def remove(callback)
        @callbacks.delete_at(@callbacks.index(callback) || @callbacks.length)
      end
    
      def event_name
        @event
      end
    end
    

    用法:

    $infinite_break = 10
    
    def infinite_loop_a(type)
      puts "#{$infinite_break} points of #{type} damage taken"
      $infinite_break -= 1
    
      if $infinite_break > 0
        $infinite.trigger(type)
      else
        $infinite.remove(:infinite_loop_a)
      end
    end
    
    def infinite_loop_b(type)
      puts "player is dealing #{$infinite_break} damage"
    end
    
    $infinite = Event.new(:infinite_loop_b, :infinite_loop_a)
    $infinite.trigger('fire')
    

    另外,我知道我在infinite_loop_a 中调用infinite_loop_b,但这是出于特定原因。 Event 实例可以有另一个 Event 作为回调。

    【讨论】:

      猜你喜欢
      • 2012-10-22
      • 1970-01-01
      • 2015-10-16
      • 2015-09-13
      • 2014-03-19
      • 2021-06-05
      • 2012-01-21
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多