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

11.2.10. Автоматическое определение методов чтения и установки на уровне класса

Мы уже рассматривали методы attr_reader, attr_writer и attr_accessor, которые немного упрощают определение методов чтения и установки атрибутов экземпляра. А как быть с атрибутами уровня класса?

В Ruby нет аналогичных средств для их автоматического создания. Но можно написать нечто подобное самостоятельно.

В первом издании этой книги была показана хитроумная схема на основе метода class_eval. С ее помощью мы создали такие методы, как cattr_reader и cattr_writer.

Но есть более простой путь. Откроем синглетный класс и воспользуемся в нем семейством методов attr. Получающиеся переменные экземпляра для синглетного класса станут переменными экземпляра класса. Часто это оказывается лучше, чем переменные класса, поскольку они принадлежат данному и только данному классу, не распространяясь вверх и вниз по иерархии наследования.

class MyClass

 @alpha = 123          # Инициализировать @alpha.

 class << self

  attr_reader :alpha   # MyClass.alpha()

  attr_writer :beta    # MyClass.beta=()

  attr_accessor :gamma # MyClass.gamma() и

 end                   # MyClass.gamma=()

 def MyClass.look

  puts ' #@alpha, #@beta, #@gamma'

 end

 #...

end

puts MyClass.alpha # 123

MyClass.beta = 456

MyClass.gamma = 789

puts MyClass.gamma # 789

MyClass.look       # 123, 456, 789

Как правило, класс без переменных экземпляра бесполезен. Но здесь мы их для краткости опустили.

11.2.11. Поддержка различных стилей программирования

Добавить отзыв
ВСЕ ОТЗЫВЫ О КНИГЕ В ИЗБРАННОЕ

0

Вы можете отметить интересные вам фрагменты текста, которые будут доступны по уникальной ссылке в адресной строке браузера.

Отметить Добавить цитату