Книга: Программирование на языке Ruby

11.2.9. Делегирование или перенаправление

11.2.9. Делегирование или перенаправление

В Ruby есть две библиотеки, которые предлагают решение задачи о делегировании или перенаправлении вызовов методов другому объекту. Они называются delegate и forwardable; мы рассмотрим обе.

Библиотека delegate предлагает три способа решения задачи. Класс SimpleDelegator полезен, когда объект, которому делегируется управление (делегат), может изменяться на протяжении времени жизни делегирующего объекта. Чтобы выбрать объект-делегат, используется метод __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. При этом мы получаем больший контроль над делегированием.

Но если вам необходим больший контроль, то, вероятно, вы все равно осуществляете делегирование на уровне отдельных методов, а не класса в целом. Тогда лучше воспользоваться библиотекой forwardable. Вернемся к примеру очереди:

require 'forwardable'
class MyQueue
 extend Forwardable
 def initialize(obj=[])
  @queue = obj # Делегировать этому объекту.
 end
 def_delegator :@queue, :push, :enqueue
 def_delegator :@queue, :shift, :dequeue
 def_delegators :@queue, :clear, :empty?, :length, :size, :<<
 # Прочий код...
end

Как видно из этого примера, метод def_delegator ассоциирует вызов метода (скажем, enqueue) с объектом-делегатом @queue и одним из методов этого объекта (push). Иными словами, когда мы вызываем метод enqueue для объекта MyQueue, производится делегирование методу push объекта @queue (который обычно является массивом).

Обратите внимание, мы пишем :@queue, а не :queue или @queue. Объясняется это тем, как написан класс Forwardable; можно было бы сделать и по-другому.

Иногда нужно делегировать методы одного объекта одноименным методам другого объекта. Метод 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 множественное наследование запрещено), но делегирование в той или иной форме вполне допустимо.

Оглавление книги


Генерация: 1.254. Запросов К БД/Cache: 3 / 1
поделиться
Вверх Вниз