Ich bin mir nicht sicher, welche Redewendung für Rückrufe im C-Stil in Ruby am besten geeignet ist - oder ob es etwas noch Besseres gibt (und weniger wie C). In C würde ich so etwas machen wie:
void DoStuff( int parameter, CallbackPtr callback )
{
// Do stuff
...
// Notify we're done
callback( status_code )
}
Was ist ein gutes Ruby-Äquivalent? Im Wesentlichen möchte ich eine übergebene Klassenmethode aufrufen, wenn eine bestimmte Bedingung in "DoStuff" erfüllt ist.
Antworten:
Das Rubinäquivalent, das nicht idiomatisch ist, wäre:
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
Der idiomatische Ansatz wäre, einen Block anstelle eines Verweises auf eine Methode zu übergeben. Ein Vorteil eines Blocks gegenüber einer freistehenden Methode ist der Kontext - ein Block ist ein Abschluss , sodass er auf Variablen aus dem Bereich verweisen kann, in dem er deklariert wurde. Dies reduziert die Anzahl der Parameter, die do_stuff an den Rückruf übergeben muss. Zum Beispiel:
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
quelle
&block
Notation, weil dann klar wird, dass die Methode einen Block nimmt, wenn man nur die erste Zeile der Definition betrachtet.Dieser "idiomatische Block" ist ein zentraler Bestandteil des alltäglichen Ruby und wird häufig in Büchern und Tutorials behandelt. Der Ruby-Informationsabschnitt enthält Links zu nützlichen [Online-] Lernressourcen.
Der idiomatische Weg ist, einen Block zu verwenden:
def x(z) yield z # perhaps used in conjunction with #block_given? end x(3) {|y| y*y} # => 9
Oder vielleicht zu einem Proc konvertiert ; hier zeige ich, dass der "Block", der implizit in einen Proc konvertiert wurde
&block
, nur ein weiterer "aufrufbarer" Wert ist:def x(z, &block) callback = block callback.call(z) end # look familiar? x(4) {|y| y * y} # => 16
(Verwenden Sie das obige Formular nur, um den Block-Now-Proc für die spätere Verwendung oder in anderen Sonderfällen zu speichern, da dadurch Overhead- und Syntaxrauschen hinzugefügt wird.)
Ein Lambda kann jedoch genauso einfach verwendet werden (dies ist jedoch nicht idiomatisch):
def x(z,fn) fn.call(z) end # just use a lambda (closure) x(5, lambda {|y| y * y}) # => 25
Während die oben genannten Ansätze können alle einpacken „Aufruf einer Methode“ , wie sie Verschlüsse schaffen, gebundene Methoden können auch als First-Class - aufrufbare Objekte behandelt werden:
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
Außerdem ist es manchmal nützlich, die
#send
Methode zu verwenden (insbesondere wenn eine Methode namentlich bekannt ist). Hier wird ein Zwischenmethodenobjekt gespeichert, das im letzten Beispiel erstellt wurde. Ruby ist ein System zur Nachrichtenübermittlung:# Using A from previous def x(z, a): a.__send__(:b, z) end x(8, A.new) # => 64
Viel Spaß beim Codieren!
quelle
Das Thema etwas genauer untersucht und den Code aktualisiert.
Die folgende Version ist ein Versuch, die Technik zu verallgemeinern, obwohl sie äußerst vereinfacht und unvollständig bleibt.
Ich habe die Implementierung von Rückrufen von DataMapper weitgehend gestohlen, Inspiration gefunden, was mir ziemlich vollständig und schön erscheint.
Ich empfehle dringend, einen Blick auf den Code @ http://github.com/datamapper/dm-core/blob/master/lib/dm-core/support/hook.rb zu werfen
Der Versuch, die Funktionalität mit dem Observable-Modul zu reproduzieren, war jedenfalls sehr ansprechend und lehrreich. Ein paar Anmerkungen:
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...
Diese einfache Darstellung der Verwendung von Observable könnte nützlich sein: http://www.oreillynet.com/ruby/blog/2006/01/ruby_design_patterns_observer.html
quelle
Also, das mag sehr "un-rubin" sein, und ich bin kein "professioneller" Ruby-Entwickler. Wenn ihr also klatscht, seid bitte sanft :)
Ruby hat ein eingebautes Modul namens Observer. Ich fand es nicht einfach zu bedienen, aber um fair zu sein, gab ich ihm keine große Chance. In meinen Projekten habe ich meinen eigenen EventHandler-Typ erstellt (ja, ich verwende häufig C #). Hier ist die Grundstruktur:
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
Um dies zu verwenden, mache ich es als schreibgeschütztes Mitglied einer Klasse verfügbar:
class Foo attr_reader :some_value_changed def initialize @some_value_changed = EventHandler.new end end
Kunden der Klasse "Foo" können ein Ereignis wie das folgende abonnieren:
foo.some_value_changed.add_listener(self, lambda { some_func })
Ich bin mir sicher, dass dies kein idiomatischer Ruby ist und ich habe gerade meine C # -Erfahrung in eine neue Sprache gebracht, aber es hat für mich funktioniert.
quelle
Wenn Sie bereit sind, ActiveSupport (von Rails) zu verwenden, haben Sie eine einfache Implementierung
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
quelle
Ich implementiere häufig Rückrufe in Ruby wie im folgenden Beispiel. Es ist sehr bequem zu bedienen.
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
quelle
Ich weiß, dass dies ein alter Beitrag ist, aber andere, die darauf stoßen, finden meine Lösung möglicherweise hilfreich.
http://chrisshepherddev.blogspot.com/2015/02/callbacks-in-pure-ruby-prepend-over.html
quelle