Ruby Language
Передача сообщений
Поиск…
Вступление
В объектно-ориентированном дизайне объекты получают сообщения и отвечают на них. В Ruby отправка сообщения вызывает метод, и результатом этого метода является ответ.
В Ruby передача сообщений динамическая. Когда сообщение приходит, а не точно знает, как ответить на него, Ruby использует предопределенный набор правил для поиска метода, который может ответить на него. Мы можем использовать эти правила для прерывания и ответа на сообщение, отправки его другому объекту или изменения его среди других действий.
Каждый раз, когда объект получает сообщение, Ruby проверяет:
- Если этот объект имеет одноэлементный класс, он может ответить на это сообщение.
- Выбирает класс объекта, а затем класс «предки».
- Один за другим проверяет, доступен ли метод этому предку и перемещается вверх по цепочке.
Сообщение, проходящее через цепочку наследования
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
Чтобы найти подходящий метод для SubExample#subexample_method
Ruby сначала рассмотрит цепочку предков SubExample
SubExample.ancestors # => [SubExample, Example, Object, Kernel, BasicObject]
Он начинается с SubExample
. Если мы отправляем сообщение subexample_method
Ruby выбирает один доступный один SubExample и игнорирует Example#subexample_method
.
s.subexample_method # => :subexample
После SubExample
он проверяет Example
. Если мы отправим SubExample
example_method
Ruby, если SubExample
может ответить на него или нет, и поскольку он не может Ruby подниматься по цепочке и просматривает Example
.
s.example_method # => :example
После того, как Ruby проверяет все определенные методы, он запускает method_missing
чтобы узнать, может ли он ответить или нет. Если мы отправим missing_subexample_method
Ruby не сможет найти определенный метод в SubExample
чтобы он перемещался до Example
. Он не может найти определенный метод на Example
или любой другой класс, выше в цепочке. Ruby запускается и запускает method_missing
. method_missing
SubExample
может ответить на missing_subexample_method
.
s.missing_subexample_method # => :subexample
Однако, если метод определен, Ruby использует определенную версию, даже если она выше в цепочке. Например, если мы отправляем not_missed_method
хотя method_missing
SubExample
может ответить на него, Ruby подходит к SubExample
потому что он не имеет определенного метода с этим именем и смотрит в Example
который имеет один.
s.not_missed_method # => :example
Прохождение сообщения через композицию модуля
Ruby движется вверх по цепи предков объекта. Эта цепочка может содержать как модули, так и классы. Те же правила о продвижении цепи распространяются и на модули.
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
Прерывание сообщений
Существует два способа прерывания сообщений.
- Используйте
method_missing
для прерывания любого не определенного сообщения. - Определите метод в середине цепочки для перехвата сообщения
После прерывания сообщений можно:
- Ответьте им.
- Отправьте их в другое место.
- Измените сообщение или его результат.
Прерывание через method_missing
и ответ на сообщение:
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
Перехват сообщения и его изменение:
class Example
def initialize title, body
end
end
class SubExample < Example
end
Теперь давайте представим, что наши данные «title: body», и мы должны разбить их перед вызовом « Example
. Мы можем определить initialize
в SubExample
.
class SubExample < Example
def initialize raw_data
processed_data = raw_data.split ":"
super processed_data[0], processed_data[1]
end
end
Перехват сообщения и отправка его другому объекту:
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