如何在Ruby中实现"回调"?

Jus*_*cle 71 ruby callback

我不确定Ruby中C风格回调的最佳成语 - 或者是否有更好的东西(而不是像C).在C中,我会做类似的事情:

void DoStuff( int parameter, CallbackPtr callback )
{
  // Do stuff
  ...
  // Notify we're done
  callback( status_code )
}
Run Code Online (Sandbox Code Playgroud)

什么是相当好的Ruby?基本上我想在"DoStuff"中遇到某个条件时调用传入的类方法

dst*_*rkr 85

红宝石的等价物,不是惯用的,将是:

def my_callback(a, b, c, status_code)
  puts "did stuff with #{a}, #{b}, #{c} and got #{status_code}"
end

def do_stuff(a, b, c, callback)
  sum = a + b + c
  callback.call(a, b, c, sum)
end

def main
  a = 1
  b = 2
  c = 3
  do_stuff(a, b, c, method(:my_callback))
end
Run Code Online (Sandbox Code Playgroud)

惯用的方法是传递一个块而不是一个方法的引用.块相对于独立方法的一个优点是上下文 - 块是闭包,因此它可以引用声明它的作用域中的变量.这减少了do_stuff需要传递给回调的参数数量.例如:

def do_stuff(a, b, c, &block)
  sum = a + b + c
  yield sum
end

def main
  a = 1
  b = 2
  c = 3
  do_stuff(a, b, c) { |status_code|
    puts "did stuff with #{a}, #{b}, #{c} and got #{status_code}"
  }
end
Run Code Online (Sandbox Code Playgroud)

  • 我仍然喜欢使用`&block`表示法,因为很明显该方法仅通过查看定义的第一行来获取块. (36认同)
  • 如果您正在使用yield,则不需要在参数列表中使用&阻止. (14认同)

小智 77

这个"习惯性的块"是日常Ruby的核心部分,在书籍和教程中经常被涵盖.Ruby的信息部分提供了指向有用[在线]学习资源.


惯用的方法是使用一个块:

def x(z)
  yield z   # perhaps used in conjunction with #block_given?
end
x(3) {|y| y*y}  # => 9
Run Code Online (Sandbox Code Playgroud)

或者也许转换为Proc ; 在这里,我展示了"块",隐式转换为Proc &block,只是另一个"可调用"值:

def x(z, &block)
  callback = block
  callback.call(z)
end

# look familiar?
x(4) {|y| y * y} # => 16
Run Code Online (Sandbox Code Playgroud)

(仅使用上面的表格来保存block-now-Proc以供以后使用或在其他特殊情况下使用,因为它会增加开销和语法噪音.)

但是,lambda可以很容易地使用(但这不是惯用的):

def x(z,fn)
  fn.call(z)
end

# just use a lambda (closure)
x(5, lambda {|y| y * y}) # => 25
Run Code Online (Sandbox Code Playgroud)

虽然上面的方法都可以在创建闭包时包装 "调用方法",但绑定的方法也可以被视为第一类可调用对象:

class A
  def b(z)
    z*z
  end
end

callable = A.new.method(:b)
callable.call(6) # => 36

# and since it's just a value...
def x(z,fn)
  fn.call(z)
end
x(7, callable) # => 49
Run Code Online (Sandbox Code Playgroud)

此外,有时使用该#send方法很有用(特别是如果方法已知名称).这里它保存了一个在上一个例子中创建的中间Method对象; Ruby是一个消息传递系统:

# Using A from previous
def x(z, a):
  a.__send__(:b, z)
end
x(8, A.new) # => 64
Run Code Online (Sandbox Code Playgroud)

快乐的编码!


Luc*_*caB 6

更多地探讨了主题并更新了代码.

以下版本试图概括该技术,尽管仍然极其简化和不完整.

我在很大程度上偷了 - 下摆,发现了灵感 - 实现了DataMapper的回调,这在我看来非常完整和美丽.

我强烈建议您查看代码@ http://github.com/datamapper/dm-core/blob/master/lib/dm-core/support/hook.rb

无论如何,尝试使用Observable模块重现功能是非常有吸引力和有益的.几点说明:

  • 添加的方法似乎是必需的,因为在注册回调时原始实例方法不可用
  • 包含的类是观察者和自我观察者
  • 该示例仅限于实例方法,不支持块,args等

码:

require 'observer'

module SuperSimpleCallbacks
  include Observable

  def self.included(klass)
    klass.extend ClassMethods
    klass.initialize_included_features
  end

  # the observed is made also observer
  def initialize
    add_observer(self)
  end

  # TODO: dry
  def update(method_name, callback_type) # hook for the observer
    case callback_type
    when :before then self.class.callbacks[:before][method_name.to_sym].each{|callback| send callback}
    when :after then self.class.callbacks[:after][method_name.to_sym].each{|callback| send callback}
    end
  end

  module ClassMethods
    def initialize_included_features
      @callbacks = Hash.new
      @callbacks[:before] = Hash.new{|h,k| h[k] = []}
      @callbacks[:after] = @callbacks[:before].clone
      class << self
        attr_accessor :callbacks
      end
    end

    def method_added(method)
      redefine_method(method) if is_a_callback?(method)
    end

    def is_a_callback?(method)
      registered_methods.include?(method)
    end

    def registered_methods
      callbacks.values.map(&:keys).flatten.uniq
    end

    def store_callbacks(type, method_name, *callback_methods)
      callbacks[type.to_sym][method_name.to_sym] += callback_methods.flatten.map(&:to_sym)
    end

    def before(original_method, *callbacks)
      store_callbacks(:before, original_method, *callbacks)
    end

    def after(original_method, *callbacks)
      store_callbacks(:after, original_method, *callbacks)
    end

    def objectify_and_remove_method(method)
      if method_defined?(method.to_sym)
        original = instance_method(method.to_sym)
        remove_method(method.to_sym)
        original
      else
        nil
      end
    end

    def redefine_method(original_method)
      original = objectify_and_remove_method(original_method)
      mod = Module.new
      mod.class_eval do
        define_method(original_method.to_sym) do
          changed; notify_observers(original_method, :before)
          original.bind(self).call if original
          changed; notify_observers(original_method, :after)
        end
      end
      include mod
    end
  end
end


class MyObservedHouse
  include SuperSimpleCallbacks

  before :party, [:walk_dinosaure, :prepare, :just_idle]
  after :party, [:just_idle, :keep_house, :walk_dinosaure]

  before :home_office, [:just_idle, :prepare, :just_idle]
  after :home_office, [:just_idle, :walk_dinosaure, :just_idle]

  before :second_level, [:party]

  def home_office
    puts "learning and working with ruby...".upcase
  end

  def party
    puts "having party...".upcase
  end

  def just_idle
    puts "...."
  end

  def prepare
    puts "preparing snacks..."
  end

  def keep_house
    puts "house keeping..."
  end

  def walk_dinosaure
    puts "walking the dinosaure..."
  end

  def second_level
    puts "second level..."
  end
end

MyObservedHouse.new.tap do |house|
  puts "-------------------------"
  puts "-- about calling party --"
  puts "-------------------------"

  house.party

  puts "-------------------------------"
  puts "-- about calling home_office --"
  puts "-------------------------------"

  house.home_office

  puts "--------------------------------"
  puts "-- about calling second_level --"
  puts "--------------------------------"

  house.second_level
end
# => ...
# -------------------------
# -- about calling party --
# -------------------------
# walking the dinosaure...
# preparing snacks...
# ....
# HAVING PARTY...
# ....
# house keeping...
# walking the dinosaure...
# -------------------------------
# -- about calling home_office --
# -------------------------------
# ....
# preparing snacks...
# ....
# LEARNING AND WORKING WITH RUBY...
# ....
# walking the dinosaure...
# ....
# --------------------------------
# -- about calling second_level --
# --------------------------------
# walking the dinosaure...
# preparing snacks...
# ....
# HAVING PARTY...
# ....
# house keeping...
# walking the dinosaure...
# second level...
Run Code Online (Sandbox Code Playgroud)

这种使用Observable的简单介绍可能很有用:http://www.oreillynet.com/ruby/blog/2006/01/ruby_design_patterns_observer.html