2009-11-05 11 views
64

Nie jestem pewien najlepszego idiomu oddzwaniania w stylu C w Ruby - lub jeśli jest coś jeszcze lepszego (i mniej jak C). W C, zrobiłbym coś takiego:Jak zaimplementować "wywołanie zwrotne" w Ruby?

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

Co to jest dobry odpowiednik Ruby? Zasadniczo chcę wywołać metodę zaliczoną w klasie, gdy pewien warunek jest spełniony w ramach "DoStuff"

+0

Może to być przydatne: https://github.com/krisleech/wisper – Kris

Odpowiedz

79

Równoważnik rubin, który nie jest idiomatycznym byłoby:

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 

idiomatycznym podejście będzie przechodzić blok a nie w odniesieniu do sposobu. Jedną z zalet bloku jest metoda wolnostojąca to kontekst - blok jest to closure, więc może odnosić się do zmiennych z zakresu, w którym został zadeklarowany. Ogranicza to liczbę parametrów, które do_stuff musi przekazać do wywołania zwrotnego. Na przykład:

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

Jeśli Używasz plonu, nie potrzebujesz i nie blokujesz na liście argumentów. – Douglas

+33

Wciąż lubię używać notacji '& block', ponieważ wtedy staje się jasne, że metoda przyjmuje blok, patrząc tylko na pierwszą linię definicji. –

+0

Zgadzam się z komentarzem @Douglas; the & block mnie wszystkich dziwne :( –

73

Ten "idiomatyczny blok" jest bardzo podstawową częścią codziennej Ruby i jest często omawiany w książkach i tutorialach. The Ruby information section zawiera linki do przydatnych zasobów edukacyjnych [online].


idiomatycznym sposobem jest wykorzystanie bloku:

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

Albo przetwarzany na Proc; tutaj pokazuję, że „blok”, przekształcony w Proc niejawnie z &block, jest tylko kolejnym „wymagalne” wartość:

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

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

(używać tylko powyższy formularz, by zapisać blok-now-Proc do późniejszego wykorzystania lub w innych szczególnych przypadkach, jak dodaje napowietrznych i hałas składnia)

jednak lambda można używać równie łatwo (ale to nie jest idiomatyczne).

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

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

Choć powyższe podejścia mogą wszystko okład "wywołanie a Metoda”, jako że tworzenie zamknięcia związany Methods mogą być również traktowane jako pierwszorzędnych płatnych na żądanie obiektów:

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 

Ponadto, czasami użyteczne do stosowania metody #send (w szczególności jeśli sposób jest znany z nazwy). Tutaj zapisuje obiekt obiektu pośredniego, który został utworzony w ostatnim przykładzie; Ruby to system przesyłania wiadomości:

# Using A from previous 
def x(z, a): 
    a.__send__(:b, z) 
end 
x(8, A.new) # => 64 

Szczęśliwe kodowanie!

+0

To był również miły wydania: http://www.rubytapas.com/episodes/35-Callable – Manav

3

Tak, to może być bardzo „un-ruby”, a ja nie jestem „profesjonalny” programista Ruby, więc jeśli macie zamiar klapsa być, być delikatny proszę :)

Ruby posiada wbudowany -int moduł o nazwie Observer. Nie uważam, że jest to łatwe w użyciu, ale żeby być uczciwym, nie dałem mu wiele okazji. W moich projektach uciekałem się do stworzenia własnego typu EventHandler (tak, używam C# dużo). Oto podstawowa struktura:

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 

Tak, aby skorzystać z tej wystawiać go jako tylko do odczytu członek klasy:

class Foo 

    attr_reader :some_value_changed 

    def initialize 
    @some_value_changed = EventHandler.new 
    end 

end 

Klienci klasy „foo” można zapisać się do imprezy jak ta :

foo.some_value_changed.add_listener(self, lambda { some_func }) 

Jestem pewien, że to nie idiomatycznych Ruby i jestem po prostu shoehorning mój C# doświadczenia w nowym języku, ale to h jak dla mnie pracował.

0

Często realizuję wywołania zwrotne w języku Ruby, jak w poniższym przykładzie. Jest bardzo wygodny w użyciu.

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

Odkryto nieco więcej informacji i zaktualizowano kod.

Następująca wersja jest próbą uogólnienia techniki, choć pozostaje wyjątkowo uproszczona i niekompletna.

I w dużej mierze ukradł - hem, znaleźć inspirację w - realizacji callbacków DataMapper, który wydaje mi się całkiem kompletny i piękny.

Sugeruję, aby rzucić okiem na kod @http://github.com/datamapper/dm-core/blob/master/lib/dm-core/support/hook.rb

Zresztą, starając się odtworzyć funkcjonalność za pomocą modułu Obserwowalne było całkiem interesujące i pouczające. Kilka uwag:

  • metoda dodania wydaje się wymagać, ponieważ oryginalne metody instancji nie są dostępne w momencie zarejestrowania wywołania zwrotne
  • tym klasa jest skierowane zarówno obserwowany i self-obserwatora
  • z przykładem jest ograniczony do metody instancji, nie obsługuje bloki, args i tak dalej

Kod:

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... 

Ta prosta prezentacja wykorzystania obserwowalnych mogłyby być użyteczne: http://www.oreillynet.com/ruby/blog/2006/01/ruby_design_patterns_observer.html

+0

To jest wielki zasób, dziękuję – gregf

+0

Mam nadzieję, że nie masz nic przeciwko, ale żłobiłem twój kod i przerobiłem go trochę na mój obecny projekt. Jest to uproszczone, ale teraz masz - nie krępuj się krytykować/żałować, itp. https://github.com/davesims/Simple-AOP/blob/master/lib/simple_aop.rb –

+0

Jedno pytanie, nad którym pracowałem - dlaczego używałeś Observable ?Miałem zderzenie nazwy metody z klasą, z którą musiałem się zintegrować, i proste wywołanie metody instancji (trigger_callbacks) działało bez zarzutu. –

Powiązane problemy