Как реализовать "обратный вызов" в Ruby?
Я не уверен в лучшей идиоме для обратных вызовов стиля C в Ruby-или если есть что - то еще лучше ( и меньше похоже на C ). В C, я бы сделал что-то вроде:
void DoStuff( int parameter, CallbackPtr callback )
{
// Do stuff
...
// Notify we're done
callback( status_code )
}
что такое хороший рубиновый эквивалент? По сути, я хочу вызвать переданный в классе метод, когда определенное условие выполняется в пределах "DoStuff"
7 ответов:
эквивалент 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идиоматический подход будет заключаться в передаче блока вместо ссылки на метод. Одним из преимуществ блока перед автономным методом является контекст-блок является закрытие, поэтому он может ссылаться на переменные из области, в которой он был объявлен. Это сокращает количество параметров, которые 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 и часто рассматривается в книгах и учебниках. The Руби информационный раздел предоставляет ссылки на полезные [Электронный ресурс] / / образовательные ресурсы.
идиоматический способ заключается в использовании блока:
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(используйте только приведенную выше форму, чтобы сохранить block-now-Proc для последующего использования или в других особых случаях, поскольку он добавляет накладные расходы и синтаксический шум.)
однако лямбда можно использовать так же легко (но это не идиоматично):
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способ (в частности, если метод известен по имени). Здесь он сохраняет промежуточный объект метода, который был создан в последнем примере; 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
в любом случае, попытка воспроизвести функциональность с помощью наблюдаемого модуля была довольно интересной и поучительной. Несколько нот:
- метод добавлен, кажется, требуется, потому что исходные методы экземпляра не доступны в момент регистрации обратных вызовов
- в том числе класса производится как наблюдать и само-наблюдатель
- в пример ограничен методами экземпляра, не поддерживает блоки, 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...Это простое представление использования Observable может быть полезно: http://www.oreillynet.com/ruby/blog/2006/01/ruby_design_patterns_observer.html
Итак, это может быть очень "un-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Итак, чтобы использовать это, я выставляю его как член только для чтения a класс:
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, как в следующем примере. Это очень удобно в использовании.
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
Я знаю, что это старый пост, но другие, которые сталкиваются с этим, могут найти мое решение полезным.
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
Comments