如何在Ruby中实现“callback”?

我不确定Ruby中C风格callback的最佳成语,还是有更好的东西(不像C语言)。 在C中,我会做这样的事情:

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

什么是一个好的Ruby等价物? 基本上我想调用一个传入的类方法,当“DoStuff”

ruby的等价物,这不是惯用的,将是:

 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 

惯用的方法是通过一个块而不是一个方法的引用。 块的一个优点是独立的方法是上下文 – 一个块是一个闭包 ,所以它可以引用它声明的范围内的variables。 这减less了do_stuff需要传递给callback的参数的数量。 例如:

 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 

这个“惯用块”是日常Ruby的核心部分,经常在书籍和教程中介绍。 Ruby信息部分提供了有用的[在线]学习资源的链接。


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

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

或者也许转换为Proc ; 在这里,我展示了使用&block隐式转换为Proc的“block”是另一个“callable”值:

 def x(z, &block) callback = block callback.call(z) end # look familiar? x(4) {|y| y * y} # => 16 

(仅使用上述表单来保存block-now-Proc以备后用,或者在其他特殊情况下使用,因为它会增加开销和语法噪音。)

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

 def x(z,fn) fn.call(z) end # just use a lambda (closure) x(5, lambda {|y| y * y}) # => 25 

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

 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 

另外,有时候使用#send方法是很有用的(特别是如果一个方法是通过名字知道的话)。 这里保存一个在上一个例子中创build的中间Method对象; Ruby是一个消息传递系统:

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

快乐的编码!

更多的探讨了这个话题,并更新了代码。

下面的版本是试图概括技术,尽pipe保持非常简单和不完整。

我在很大程度上偷了一下,find了灵感 – 实现了DataMapper的callback,这在我看来相当完整和美观。

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

无论如何,试图使用Observable模块来重现function是相当有吸引力和指导性的。 一些注意事项:

  • 添加方法似乎是需要的,因为原始实例方法在注册callback时不可用
  • 包括class级既是观​​察者又是自我观察者
  • 该示例仅限于实例方法,不支持块,参数等

码:

 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... 

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

所以,这可能是非常“ruby”,我不是一个“专业”的Ruby开发人员,所以如果你们打算是,请温柔请:)

Ruby有一个名为Observer的内置模块。 我还没有发现它很容易使用,但公平我没有给它一个机会。 在我的项目中,我采取了创build自己的EventHandlertypes(是的,我使用C#很多)。 这里是基本结构:

 class EventHandler def initialize @client_map = {} end def add_listener(id, func) (@client_map[id.hash] ||= []) << func end def remove_listener(id) return @client_map.delete(id.hash) end def alert_listeners(*args) @client_map.each_value { |v| v.each { |func| func.call(*args) } } end end 

所以,为了使用它,我把它作为一个类的只读成员公开:

 class Foo attr_reader :some_value_changed def initialize @some_value_changed = EventHandler.new end end 

“Foo”类的客户可以订阅这样的事件:

 foo.some_value_changed.add_listener(self, lambda { some_func }) 

我相信这不是惯用的Ruby,而我只是将我的C#经验强加给一种新的语言,但它对我来说很合适。

我经常在Ruby中实现callback,如下例所示。 使用起来非常舒适。

 class Foo # Declare a callback. def initialize callback( :on_die_cast ) end # Do some stuff. # The callback event :on_die_cast is triggered. # The variable "die" is passed to the callback block. def run while( true ) die = 1 + rand( 6 ) on_die_cast( die ) sleep( die ) end end # A method to define callback methods. # When the latter is called with a block, it's saved into a instance variable. # Else a saved code block is executed. def callback( *names ) names.each do |name| eval <<-EOF @#{name} = false def #{name}( *args, &block ) if( block ) @#{name} = block elsif( @#{name} ) @#{name}.call( *args ) end end EOF end end end foo = Foo.new # What should be done when the callback event is triggered? foo.on_die_cast do |number| puts( number ) end foo.run 

我知道这是一个旧的post,但其他碰到这可能会发现我的解决scheme有帮助。

http://chrisshepherddev.blogspot.com/2015/02/callbacks-in-pure-ruby-prepend-over.html