полезен, когда объект, которому делегируется управление (делегат), может изменяться на протяжении времени жизни делегирующего объекта. Чтобы выбрать объект-делегат, используется метод
__setobj__
.
Однако мне этот способ представляется слишком примитивным. Поскольку я не думаю, что это существенно лучше, чем то же самое, сделанное вручную, задерживаться на классе
SimpleDelegator
не стану.
Метод верхнего уровня
DelegateClass
принимает в качестве параметра класс, которому делегируется управление. Затем он создает новый класс, которому мы можем унаследовать. Вот пример создания класса
Queue
, который делегирует объекту
Array
:
require 'delegate'
class MyQueue < DelegateClass(Array)
def initialize(arg=[])
super(arg)
end
alias_method :enqueue, :push
alias_method :dequeue, :shift
end
mq = MyQueue.new
mq.enqueue(123)
mq.enqueue(234)
p mq.dequeue # 123
p mq.dequeue # 234
Можно также унаследовать класс
Delegator
и реализовать метод
__getobj__
; именно таким образом реализован класс
SimpleDelegator
. При этом мы получаем больший контроль над делегированием.
Но если вам необходим больший контроль, то, вероятно, вы все равно осуществляете делегирование на уровне отдельных методов, а не класса в целом. Тогда лучше воспользоваться библиотекой
Иногда нужно делегировать методы одного объекта одноименным методам другого объекта. Метод
def_delegators
позволяет задать произвольное число таких методов. Например, в примере выше показано, что вызов метода
length
объекта
MyQueue
приводит к вызову метода
length
объекта
@queue
.
В отличие от первого примера, остальные методы делегирующим объектом просто не поддерживаются. Иногда это хорошо, ведь не хотите же вы вызывать метод
[]
или
[]=
для очереди; если вы так поступаете, то очередь перестает быть очередью.
Отметим еще, что показанный выше код позволяет вызывающей программе передавать объект конструктору (для использования в качестве объекта-делегата). В полном соответствии с духом «утилизации» это означает, что я могу выбирать вид объекта, которому делегируется управление, коль скоро он поддерживает те методы, которые вызываются в программе.
Например, все приведенные ниже вызовы допустимы. (В последних двух предполагается, что предварительно было выполнено предложение
require 'thread'
.)
q1 = MyQueue.new # Используется любой массив.
q2 = MyQueue.new(my_array) # Используется конкретный массив.
q3 = MyQueue.new(Queue.new) # Используется Queue (thread.rb).
q4 = MyQueue.new(SizedQueue.new) # Используется SizedQueue (thread.rb).
Так, объекты
q3
и
q4
волшебным образом становятся безопасными относительно потоков, поскольку делегируют управление безопасному в этом отношении объекту (если, конечно, какой-нибудь не показанный здесь код не нарушит эту гарантию).
Существует также класс
SingleForwardable
, который воздействует на один экземпляр, а не на класс в целом. Это полезно, если вы хотите, чтобы какой-то конкретный объект делегировал управление другому объекту, а все остальные объекты того же класса так не поступали.
Быть может, вы задумались о том, что лучше — делегирование или наследование. Но это неправильный вопрос. В некоторых ситуациях делегирование оказывается более подходящим решением. Предположим, к примеру, что имеется класс, у которого уже есть родитель. Унаследовать еще от одного родителя мы не можем (в Ruby множественное наследование запрещено), но делегирование в той или иной форме вполне допустимо.
11.2.10. Автоматическое определение методов чтения и установки на уровне класса