簡體   English   中英

如何在 Ruby 中實現“回調”?

[英]How to implement a "callback" in Ruby?

我不確定 C 風格的回調在 Ruby 中的最佳習語 - 或者是否有更好的東西(而不像 C )。 在 C 中,我會做類似的事情:

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

什么是 Ruby 等價物? 本質上,當“DoStuff”中滿足特定條件時,我想調用一個傳入的 class 方法

不常見的紅寶石等效為:

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的核心部分,在書籍和教程中經常涉及。 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

(僅使用上面的格式保存立即保存的塊以備后用或在其他特殊情況下使用,因為這會增加開銷和語法干擾。)

但是,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

編碼愉快!

進一步探討了該主題,並更新了代碼。

以下版本是對技術進行概括的嘗試,盡管仍然非常簡化和不完整。

我很大程度上偷走了DataMapper的回調,並從中找到了靈感,在我看來,這很完整,很漂亮。

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

無論如何,嘗試使用Observable模塊重現該功能非常吸引人並且具有啟發性。 一些注意事項:

  • 似乎需要添加方法,因為在注冊回調時原始實例方法不可用
  • 包含類是觀察者和自我觀察者
  • 該示例僅限於實例方法,不支持塊,參數等

碼:

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有一個內置的名為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#經驗塞進一種新語言中,但這對我有用。

如果您願意使用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 class,它實現了對它們的基本操作—— additionsubtraction

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

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

我們想為每個操作添加一個回調,它將對輸入數據和結果進行處理。

在下面的示例中,我們將實現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)

Output:

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

我知道這是一篇舊文章,但是遇到此問題的其他人可能會發現我的解決方案很有幫助。

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

我經常像下面的示例一樣在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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM