Ruby Language
Przekazywanie wiadomości
Szukaj…
Wprowadzenie
W projektowaniu obiektowym obiekty odbierają wiadomości i odpowiadają na nie. W języku Ruby wysyłanie wiadomości wywołuje metodę, a wynikiem tej metody jest odpowiedź.
W Ruby przekazywanie wiadomości jest dynamiczne. Gdy wiadomość dotrze, zamiast dokładnie wiedzieć, jak na nią odpowiedzieć, Ruby używa predefiniowanego zestawu reguł, aby znaleźć metodę, która może na nią odpowiedzieć. Możemy użyć tych reguł, aby przerwać i odpowiedzieć na wiadomość, wysłać ją do innego obiektu lub zmodyfikować między innymi akcjami.
Za każdym razem, gdy obiekt otrzymuje komunikat Ruby sprawdza:
- Jeśli ten obiekt ma klasę singleton i może odpowiedzieć na tę wiadomość.
- Sprawdza klasę tego obiektu, a następnie łańcuch przodków klasy.
- Jeden po drugim sprawdza, czy metoda jest dostępna dla tego przodka i przesuwa się w górę łańcucha.
Wiadomość przekazywana przez łańcuch dziedziczenia
class Example
def example_method
:example
end
def subexample_method
:example
end
def not_missed_method
:example
end
def method_missing name
return :example if name == :missing_example_method
return :example if name == :missing_subexample_method
return :subexample if name == :not_missed_method
super
end
end
class SubExample < Example
def subexample_method
:subexample
end
def method_missing name
return :subexample if name == :missing_subexample_method
return :subexample if name == :not_missed_method
super
end
end
s = Subexample.new
Aby znaleźć odpowiednią metodę dla SubExample#subexample_method
Ruby najpierw sprawdza łańcuch przodków SubExample
SubExample.ancestors # => [SubExample, Example, Object, Kernel, BasicObject]
Zaczyna się od SubExample
. Jeśli wyślemy wiadomość z subexample_method
Ruby wybierze jedną z dostępnych SubExample i zignoruje Example#subexample_method
.
s.subexample_method # => :subexample
Po SubExample
sprawdza Example
. Jeśli wyślemy przykładową example_method
Ruby sprawdza, czy SubExample
może na nią odpowiedzieć, czy nie, a ponieważ nie może, Ruby idzie w górę łańcucha i patrzy na Example
.
s.example_method # => :example
Po tym, jak Ruby sprawdzi wszystkie zdefiniowane metody, uruchamia brak method_missing
aby sprawdzić, czy może odpowiedzieć. Jeśli mamy wysłać missing_subexample_method
Ruby nie będzie w stanie znaleźć określoną metodą na SubExample
więc przenosi się do Example
. Nie może również znaleźć zdefiniowanej metody w Example
ani żadnej innej klasie wyższej w łańcuchu. Ruby zaczyna od nowa i uruchamia brak method_missing
. method_missing
z SubExample
może odpowiedzieć missing_subexample_method
.
s.missing_subexample_method # => :subexample
Jednak jeśli metoda jest zdefiniowana, Ruby używa zdefiniowanej wersji, nawet jeśli jest wyższa w łańcuchu. Na przykład, jeśli wyślemy not_missed_method
nawet jeśli method_missing
SubExample
może na nie odpowiedzieć, Ruby podchodzi do SubExample
ponieważ nie ma zdefiniowanej metody o takiej nazwie i sprawdza Example
który ją posiada.
s.not_missed_method # => :example
Przekazywanie wiadomości przez skład modułu
Ruby przesuwa się w górę łańcucha przodków obiektu. Ten łańcuch może zawierać zarówno moduły, jak i klasy. Te same zasady dotyczące awansowania łańcucha dotyczą również modułów.
class Example
end
module Prepended
def initialize *args
return super :default if args.empty?
super
end
end
module FirstIncluded
def foo
:first
end
end
module SecondIncluded
def foo
:second
end
end
class SubExample < Example
prepend Prepended
include FirstIncluded
include SecondIncluded
def initialize data = :subexample
puts data
end
end
SubExample.ancestors # => [Prepended, SubExample, SecondIncluded, FirstIncluded, Example, Object, Kernel, BasicObject]
s = SubExample.new # => :default
s.foo # => :second
Przerywanie wiadomości
Istnieją dwa sposoby przerywania wiadomości.
- Użyj
method_missing
aby przerwać każdą niezdefiniowaną wiadomość. - Zdefiniuj metodę w środku łańcucha, aby przechwycić wiadomość
Po przerwaniu wiadomości można:
- Odpowiedz im.
- Wyślij je gdzieś indziej.
- Zmodyfikuj wiadomość lub jej wynik.
Przerwanie przez brak method_missing
i odpowiedź na wiadomość:
class Example
def foo
@foo
end
def method_missing name, data
return super unless name.to_s =~ /=$/
name = name.to_s.sub(/=$/, "")
instance_variable_set "@#{name}", data
end
end
e = Example.new
e.foo = :foo
e.foo # => :foo
Przechwytywanie wiadomości i modyfikowanie jej:
class Example
def initialize title, body
end
end
class SubExample < Example
end
Teraz wyobraźmy sobie, że nasze dane to „tytuł: ciało” i musimy je rozdzielić przed wywołaniem Example
. Możemy zdefiniować initialize
na SubExample
.
class SubExample < Example
def initialize raw_data
processed_data = raw_data.split ":"
super processed_data[0], processed_data[1]
end
end
Przechwytywanie wiadomości i wysyłanie jej do innego obiektu:
class ObscureLogicProcessor
def process data
:ok
end
end
class NormalLogicProcessor
def process data
:not_ok
end
end
class WrapperProcessor < NormalLogicProcessor
def process data
return ObscureLogicProcessor.new.process data if data.obscure?
super
end
end