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

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

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


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

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

222

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

Данную проблему можно решить путем добавления к классу Parts метода
size. Нужно реализовать метод, пересылающий сообщение size настоящему

массиву.
1
2
3

def size
parts.size
end

Однако это изменение способно свернуть класс Parts на скользкую дорожку.
Если вы именно так и сделаете, то вскоре вам захочется, чтобы Parts реагировал
на сообщение each, затем на sort, а следом и на все остальное, что есть в экземплярах класса Array. И это никогда не кончится; чем больше вы будете делать
Parts похожим на массив, тем выше будут ожидания от него качеств, присущих
массиву.
Возможно, Parts по своей сути и есть массив, хотя и с добавлением дополнительных особенностей поведения. И вы можете сделать из него массив. В следующем примере показана новая версия класса Parts, который теперь является
подклассом Array.
1 class Parts < Array
2
def spares
3
select {|part| part.needs_spare}
4
end
5 end

Данный код — весьма простое выражение идеи о том, что Parts является
специализацией Array; в идеальном объектно-ориентированном языке это решение было бы абсолютно правильным. К сожалению, язык Ruby еще не достиг
полного совершенства, и у данной конструкции имеется скрытый дефект.
Суть проблемы показана в следующем примере. Когда Parts становится
подклассом Array, он наследует все поведение Array. Это поведение включает
и метод +, который объединяет два массива и возвращает третий массив. В строках 3 и 4 метод + объединяет два существующих экземпляра Parts и сохраняет
результат в переменной combo_parts.
Создается впечатление, что этот код работает, и теперь combo_parts содержит
соответствующее предположениям количество частей (строка 7). Но кое-что
явно не в порядке. Как показано в строке 12, combo_parts не в состоянии ответить
на вопрос о запчастях spares.
Основная причина проблемы раскрывается в строках 15–17. Хотя объекты,
объединенные с помощью метода +, были экземплярами Parts, объект, воз-

   223
Составление композиции для объекта Parts  

вращенный методом +, стал экземпляром Array, а Array не понимает сообщения spares.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# Parts наследует '+' из Array, поэтому вы можете
# собрать два объекта Parts вместе.
combo_parts =
(mountain_bike.parts + road_bike.parts)
# '+' конечно же объединяет Parts
combo_parts.size # -> 7
# но объект, возвращаемый '+',
# не понимает сообщения 'spares'
combo_parts.spares
# -> NoMethodError: метод 'spares'
#
для # не определен
mountain_bike.parts.class # -> Parts
road_bike.parts.class
# -> Parts
combo_parts.class
# -> Array !!!

Дело в том, что в Array имеется множество методов, возвращающих новые
экземпляры класса Array, но не новые экземпляры ваших подклассов. Класс
Parts по-прежнему вводит вас в заблуждение, получается, вы просто подменили одну проблему другой. Ранее вы расстраивались, обнаружив отсутствие
в классе Parts реализации метода size, а теперь можете удивиться, обнаружив,
что объединение двух Parts-объектов возвращает результат, которому непонятно сообщение spares.
Вы уже видели три различные реализации класса Parts. Первая реагировала
только на сообщения spares и parts и своим поведением не была похожа на
массив, она просто содержала массив данных. Во вторую реализацию класса
Parts в качестве небольшого усовершенствования был добавлен метод size,
возвращающий размер его внутреннего массива. Самая последняя версия реализации класса Parts стала подклассом Array, в результате чего в ней появилось
полноценное поведение, присущее массиву, но, как показал приведенный ранее
пример, экземпляр Parts по-прежнему демонстрирует неожиданные стороны
своего поведения.
Становится ясно, что идеального решения не существует, поэтому настала
пора принять трудное решение. Пусть исходная реализация не в состоянии
реагировать на сообщение size, но она способна проявить себя достаточно

224

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

хорошо, при этом можно смириться с отсутствием у нее поведения, присущего
массивам, и вернуться к ее использованию. Если нужна реакция на size (и только на size), возможно, лучше добавить только этот один метод и довольствоваться предложенным вторым вариантом реализации. Если же вы можете
смириться с возможностью возникновения досадных ошибок или абсолютно
точно знаете, что никогда не столкнетесь с ними, то, возможно, есть смысл сделать реализацию подклассом Array и закрыть вопрос.
Следующее решение находится где-то посередине между сложностью и практичностью. Класс Parts, показанный ниже, перенаправляет сообщения size
и each своему массиву