Je ne suis pas sûr du meilleur idiome pour les rappels de style C en Ruby - ou s’il ya encore mieux (et moins comme C). En C, je ferais quelque chose comme:
void DoStuff( int parameter, CallbackPtr callback )
{
// Do stuff
...
// Notify we're done
callback( status_code )
}
Quel est un bon équivalent Ruby? Je veux essentiellement appeler une méthode passée en classe, lorsqu'une certaine condition est remplie dans "DoStuff"
L'équivalent Ruby, qui n'est pas idiomatique, serait:
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
L'approche idiomatique serait de passer un bloc au lieu d'une référence à une méthode. L'un des avantages d'un bloc par rapport à une méthode autonome est le contexte - un bloc est une fermeture , de sorte qu'il peut faire référence à des variables du champ dans lequel il a été déclaré. Cela réduit le nombre de paramètres que do_stuff doit transmettre au rappel. Par exemple:
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
Ce "bloc idiomatique" est une partie essentielle du quotidien Ruby et est fréquemment traité dans des livres et des tutoriels. La section Ruby information information fournit des liens vers des ressources d’apprentissage utiles [en ligne].
La manière idiomatique est d'utiliser un bloc:
def x(z)
yield z # perhaps used in conjunction with #block_given?
end
x(3) {|y| y*y} # => 9
Ou peut-être converti en Proc ; je montre ici que le "bloc", converti implicitement en Proc avec &block
, est juste une autre valeur "appelable":
def x(z, &block)
callback = block
callback.call(z)
end
# look familiar?
x(4) {|y| y * y} # => 16
(Utilisez uniquement le formulaire ci-dessus pour enregistrer le bloc block-now-Proc en vue d'une utilisation ultérieure ou dans d'autres cas particuliers, car il ajoute une surcharge et un bruit de syntaxe.)
Cependant, un lambda peut être utilisé aussi facilement (mais ce n’est pas idiomatique):
def x(z,fn)
fn.call(z)
end
# just use a lambda (closure)
x(5, lambda {|y| y * y}) # => 25
Alors que les approches ci-dessus peuvent toutes wrap "appeler une méthode" car elles créent des fermetures, bound Methods peut également être traité comme des objets appelables de première classe:
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
De plus, il est parfois utile d’utiliser la méthode #send
(en particulier si une méthode est connue par son nom). Ici, il enregistre un objet Méthode intermédiaire créé dans le dernier exemple. Ruby est un système de transmission de messages:
# Using A from previous
def x(z, a):
a.__send__(:b, z)
end
x(8, A.new) # => 64
Bonne codage!
Exploré le sujet un peu plus et mis à jour le code.
La version suivante tente de généraliser la technique, tout en restant extrêmement simplifiée et incomplète.
J'ai surtout volé, trouvé l'inspiration dans la mise en œuvre des rappels de DataMapper, ce qui me semble assez complet et magnifique.
Je suggère fortement de consulter le code @ http://github.com/datamapper/dm-core/blob/master/lib/dm-core/support/hook.rb
Quoi qu’il en soit, essayer de reproduire la fonctionnalité à l’aide du module Observable s’est avéré très intéressant et instructif ..___ Quelques remarques:
code:
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...
Cette présentation simple de l'utilisation de Observable pourrait être utile: http://www.oreillynet.com/Ruby/blog/2006/01/Ruby_design_patterns_observer.html
Donc, cela peut être très "un-Ruby", et je ne suis pas un développeur Ruby "professionnel", alors si vous allez être fous, soyez gentils s'il vous plait :)
Ruby a un module intégré appelé Observer. Je ne l'ai pas trouvé facile à utiliser, mais pour être juste, je ne lui ai pas donné beaucoup de chance. Dans mes projets, j'ai eu recours à la création de mon propre type EventHandler (oui, j'utilise beaucoup le C #). Voici la structure de base:
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
Donc, pour utiliser cela, je l'expose en tant que membre en lecture seule d'une classe:
class Foo
attr_reader :some_value_changed
def initialize
@some_value_changed = EventHandler.new
end
end
Les clients de la classe "Foo" peuvent s'abonner à un événement comme celui-ci:
foo.some_value_changed.add_listener(self, lambda { some_func })
Je suis sûr que ce n'est pas idiomatique, Ruby, et je ne fais que transformer mon expérience en C # dans une nouvelle langue, mais cela a fonctionné pour moi.
Je sais que ceci est un ancien post, mais les autres qui le trouvent peuvent trouver ma solution utile.
http://chrisshepherddev.blogspot.com/2015/02/callbacks-in-pure-Ruby-prepend-over.html
J'implémente souvent des rappels en Ruby, comme dans l'exemple suivant. C'est très confortable à utiliser.
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
Si vous souhaitez utiliser ActiveSupport (de Rails), vous avez une implémentation simple.
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