如何在 Ruby 中实现“回调”?

我不确定 Ruby 中 C 风格回调的最佳习惯用法是什么,或者是否有更好的方法(不像 C)。在 C 中,我会这样做:

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

什么是好的 Ruby 等价物?当“ DoStuff”中满足某个条件时,本质上我想调用一个传入的类方法

61806 次浏览

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


惯用的方法是使用块:

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

或者也可以转换为 行动; 这里我展示了“块”,通过 &block隐式转换为 Proc,只是另一个“可调用”值:

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

虽然上述方法都可以在创建闭包时 包起来“调用方法”,但绑定的 方法也可以被视为一级可调用对象:

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方法也是有用的(特别是如果方法是按名称命名的)。这里它保存了上一个示例中创建的一个中间 Method 对象; Ruby 是一个消息传递系统:

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

编程愉快!

因此,这可能是非常“不 Ruby”的,而且我不是一个“专业”的 Ruby 开发人员,所以如果你们要打击的话,请温柔一点:)

Ruby 有一个内置的名为 Observer 的模块。我发现它并不容易使用,但公平地说,我没有给它太多的机会。在我的项目中,我使用创建自己的 EventHandler 类型(是的,我经常使用 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 # 经验硬塞到一种新语言中,但它对我很有用。

红宝石的等价物是:

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

惯用的方法是传递一个块而不是一个对方法的引用。块相对于独立方法的一个优势是上下文——块是 了结,因此它可以引用声明它的作用域中的变量。这减少了 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

我经常像下面的例子一样在 Ruby 中实现回调。

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

进一步研究了该主题并更新了代码。

下面的版本试图推广这种技术,尽管仍然非常简单和不完整。

我在 DataMapper 回调的实现中获得了很大的灵感,在我看来,它相当完整和美丽。

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

无论如何,尝试使用观察模块重现功能是相当吸引人的和有教育意义的。 一些注意事项:

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

密码:

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

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

我知道这是一个老职位,但其他人遇到这可能会发现我的解决方案有帮助。

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

如果您愿意使用 ActiveSupport(来自 Rails) ,那么您就有了一个简单的实现

class ObjectWithCallbackHooks
include ActiveSupport::Callbacks
define_callbacks :initialize # Your object supprots an :initialize callback chain


include ObjectWithCallbackHooks::Plugin


def initialize(*)
run_callbacks(:initialize) do # run `before` callbacks for :initialize
puts "- initializing" # then run the content of the block
end # then after_callbacks are ran
end
end


module ObjectWithCallbackHooks::Plugin
include ActiveSupport::Concern


included do
# This plugin injects an "after_initialize" callback
set_callback :initialize, :after, :initialize_some_plugin
end
end

我知道这是一个老职位,但我发现它时,试图解决类似的问题。

这是一个非常优雅的解决方案,最重要的是,不管有没有回调,它都能正常工作。


假设我们有一个 Arithmetic类,它对它们实现基本操作ーー additionsubtraction

class Arithmetic
def addition(a, b)
a + b
end


def subtraction(a, b)
a - b
end
end

我们希望为每个操作添加一个回调函数,它将对输入数据和结果执行某些操作。

在下面的例子中,我们将实现接受 Ruby 块的 after_operation方法,这个 Ruby 块将在一个操作之后执行。

class Arithmetic
def after_operation(&block)
@after_operation_callback = block
end


def addition(a, b)
do_operation('+', a, b)
end


def subtraction(a, b)
do_operation('-', a, b)
end


private


def do_operation(sign, a, b)
result =
case sign
when '+'
a + b
when '-'
a - b
end


if callback = @after_operation_callback
callback.call(sign, a, b, result)
end


result
end
end

与回调一起使用:

callback = -> (sign, a, b, result) do
puts "#{a} #{sign} #{b} = #{result}"
end


arithmetic = Arithmetic.new
arithmetic.after_operation(&callback)


puts arithmetic.addition(1, 2)
puts arithmetic.subtraction(3, 1)

产出:

1 + 2 = 3
3
3 - 1 = 2
2