2009-11-05 85 views
64

我不確定C語言回調在Ruby中的最佳成語 - 或者如果有更好的東西(而不是C)。在C中,我會這樣做:如何在Ruby中實現「回調」?

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

什麼是好的Ruby等價物?實質上,我想調用傳入的類方法,當在「DoStuff」中遇到某些條件時

+0

這可能是有用的:https://github.com/krisleech/wisper – Kris 2013-05-12 12:02:49

回答

79

紅寶石當量,這是不慣用,將是:

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 

的慣用的做法是通過代替的方法的參考塊。塊獨立於塊獨立方法的一個優點是上下文 - 塊是closure,所以它可以引用它聲明的範圍中的變量。這減少了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 
+13

如果您'使用收益率,你不需要阻止參數列表。 – Douglas 2010-09-03 19:16:31

+33

我仍然喜歡使用'&block'符號,因爲這樣很明顯該方法只是通過查看定義的第一行來獲取一個塊。 – 2012-07-21 18:01:40

+0

同意w/@Douglas評論;這個塊讓我很奇怪:( – 2015-04-15 15:50:41

73

此「常用塊」是日常Ruby的核心部分,常常在書籍和教程中進行介紹。Ruby information section提供了有用[在線]學習資源的鏈接。


的慣用的方法是使用一個塊:

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

或許轉換爲Proc;這裏我顯示,「塊」,轉換成一個Proc隱含地&block,僅僅是另一個「可調用」值:

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

# look familiar? 
x(4) {|y| y * y} # => 16 

(僅使用上述形式來保存塊現在進程內用於以後使用或在其他特殊情況,因爲它增加了開銷和語法噪聲)

然而,一個lambda可以使用很容易地(但這不是地道):

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

# just use a lambda (closure) 
x(5, lambda {|y| y * y}) # => 25 

雖然上面的方法都可以「打電話給方法」,因爲它們創建封閉件,結合Methods也可以作爲第一級的可調用的對象進行處理:

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 

快樂編碼!

+0

這也是一個很好的再現:http://www.rubytapas.com/episodes/35-Callable – Manav 2014-04-17 05:24:23

3

所以,這可能是很「非紅寶石」,我不是一個「專業」 Ruby開發者,所以如果你們要嫌是,要溫柔請:)

紅寶石有一個內置-int模塊名爲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.some_value_changed.add_listener(self, lambda { some_func }) 

我敢肯定這是不是慣用的Ruby和我只是shoehorning我的C#經驗變成一種新的語言,但它^ h爲我工作。

0

我經常在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 
5

探討了這個話題,並更新了代碼。

以下版本試圖推廣該技術,儘管保持極其簡化和不完整。我很大程度上偷了一下 - 找到了靈感 - 實現了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... 

這個簡單的使用可觀察到的表現可能是有用的:http://www.oreillynet.com/ruby/blog/2006/01/ruby_design_patterns_observer.html

+0

這是一個很好的資源,謝謝 – gregf 2010-09-07 18:08:43

+0

我希望你不要介意,但是我把你的代碼分開了,並且爲我當前的項目重寫了一下。 - 隨時批評/扒掉,等等。https://github.com/davesims/Simple-AOP/blob/master/lib/simple_aop.rb – 2011-04-19 01:39:55

+0

我有這個工作的一個問題 - 爲什麼你使用Observable ?我有一個方法名與我需要集成的類衝突,並且簡單地調用實例方法(trigger_callbacks)工作正常。 – 2011-04-19 01:40:56