Ruby. Объектно-ориентированное проектирование [Сэнди Метц] (pdf) читать постранично, страница - 69

-  Ruby. Объектно-ориентированное проектирование  5.85 Мб, 304с. скачать: (pdf) - (pdf+fbd)  читать: (полностью) - (постранично) - Сэнди Метц

Книга в формате pdf! Изображения и текст могут не отображаться!


 [Настройки текста]  [Cбросить фильтры]

@parts и включает Enumerable для получения общих
методов обхода элементов массива и осуществления поиска в нем. Эта версия
Parts не обладает полноценным поведением, присущим экземплярам класса
Array, но она по крайней мере выполняет все заявленные действия.
1 require 'forwardable'
2 class Parts
3
extend Forwardable
4
def_delegators :@parts, :size, :each
5
include Enumerable
6
7
def initialize(parts)
8
@parts = parts
9
end
10
11
def spares
12
select {|part| part.needs_spare}
13
end
14 end

Отправка сообщения + экземпляру этого класса Parts приводит к выдаче
исключения, связанного с отсутствием метода — NoMethodError. Но поскольку
теперь Parts реагирует на size, each и на все, что связано с перечислениями
(Enumerable), а также неизменно выдает исключения, когда этот объект ошибочно принимают за настоящий массив, с применением этого кода можно согласиться. В следующем примере показано, что теперь на сообщение size могут
реагировать и spares, и parts.
1 mountain_bike =
2
Bicycle.new(
3
size: 'L',

     225
Изготовление Parts-объектов

4
parts: Parts.new([chain,
5
mountain_tire,
6
front_shock,
7
rear_shock]))
8
9 mountain_bike.spares.size # -> 3
10 mountain_bike.parts.size # -> 4

Итак, в вашем распоряжении опять появились работоспособные версии
классов Bicycle, Parts и Part. Настало время переосмыслить конструкцию.

Изготовление
Parts-объектов
Посмотрите на строки 4–7 предыдущего примера. Part-объекты, хранящиеся
в переменных chain, mountain_tire и т. д., были созданы настолько давно, что
вы о них могли уже забыть. Подумайте об основе тех знаний, которые представлены этими четырьмя строками: где-то в вашем приложении некий объект
должен знать, как создавать эти Part-объекты. А в строках 4–7 именно это
место должно быть в курсе, что именно эти четыре конкретных объекта используются в горных велосипедах.
Такой большой объем знаний может весьма легко распространиться по всему приложению, что крайне нежелательно, да и в этом нет никакой необходимости. Несмотря на обилие различных отдельных частей, подходящих комбинаций этих частей не так уж и много. Все могло бы существенно упроститься,
если бы можно было дать описание различным велосипедам, которые затем
использовать для изготовления неким магическим образом надлежащих Partsобъектов для любой разновидности велосипеда.
Дать описание комбинациям составных частей любого конкретного велосипеда совсем нетрудно. В показанном ниже примере кода это делается с помощью
простого двумерного массива, в котором каждая строка состоит из трех столбцов.
В первом содержится название составной части ('chain', 'tire_size' и т. д.),
во втором дается ее описание ('10-speed', '23' и т. д.), в третьем (необязательном) содержится булево значение, указывающее потребность данного компонента в запчастях. Значение для третьего столбца имеется только для переднего амортизатора 'front_shock' в строке 9; что же касается других составных

226

Глава 8. Объединение объектов путем составления композиции

частей, то лучше считать, что для них по умолчанию используется значение
true, поскольку они нуждаются в запчастях.
1
2
3
4
5
6
7
8
9
10

road_config =
[['chain', '10-speed'],
['tire_size', '23'],
['tape_color', 'red']]
mountain_config =
[['chain', '10-speed'],
['tire_size', '2.1'],
['front_shock', 'Manitou', false],
['rear_shock', 'Fox']]

В отличие от хеша, этот простой двумерный массив не дает никакой
структурной информации. Но вы сами разбираетесь в организации этой
структуры и можете ее запрограммировать в новом объекте, создающем Partsобъекты.

Создание модуля
PartsFactory
Как уже упоминалось в главе 3, объект, создающий другие объекты, называется
фабрикой. Возможно, в силу вашего опыта работы с другими языками программирования это слово вызывает у вас настороженность, но данный случай
поможет восстановить его репутацию. Слово «фабрика» (factory) не служит
признаком чего-то труднопонимаемого, или искусственного, или слишком
сложного, это слово разработчики объектно-ориентированных приложений
употребляют для краткого обозначения сути объекта, создающего другие объекты. В Ruby фабрики не отличаются особой сложностью и нет смысла избегать
их применения. В приводимом ниже примере кода показывается новый модуль
PartsFactory. В его задачу входят получение массива, подобного ранее упомянутому, и изготовление Parts-объекта. Попутно он также может создавать Partобъекты, но это действие является закрытым. Его открытая обязанность — создание Parts-объекта.
Первая версия PartsFactory получает три аргумента: config, а также имена
классов, используемых для Part-объектов и Parts-объектов. В строке 6 создается новый экземпляр Parts-объекта, инициализируемый массивом, состоящим

     227
Изготовление Parts-объектов

из Part-объектов и создаваемым на основе информации, которая находится
в аргументе config.
1 module PartsFactory
2
def self.build(config,
3
part_class = Part,
4
parts_class = Parts)
5
6
parts_class.new(
7
config.collect {|part_config|
8
part_class.new(
9
name:
part_config[0],
10
description: part_config[1],
11
needs_spare: part_config.fetch(2, true))})
12
end
13 end

Структура массива config этой фабрике известна. В строках 9–11 фабрика
ожидает, что name будет в