Типичным средством пользовательского графического интерфейса являются
многоуровневые меню (в некоторых системах они называются каскадными меню),
которые требуются, когда элемент меню содержит несколько альтернативных команд.
Например, пункт главного меню для эмулятора терминала может иметь имя «задать
опции». Когда этот пункт (подменю) выбран пользователем, высвечивается второе меню,
которое позволяет выбирать из набора имеющихся опций (темный/светлый фон и т. д.).
Многоуровневое меню определенно принадлежит классу Menu. Оно содержит ту же
информацию, что и класс Menu, и должно вести себя подобным образом. С другой
стороны, оно так же, несомненно, является элементом MenuItem, так как содержит имя и
способно выполнить команду (вывести свой образ на экран), когда соответствующий
пункт выбран в родительском меню. Требуемое поведение может быть достигнуто с
минимальными усилиями, если мы разрешим классу WalkingMenu наследовать от обоих
родителей. Например, когда всплывающему меню требуется выполнить действие по
щелчку мыши (унаследованное от класса MenuItem), оно выводит на экран свое
содержимое (вызывая графический метод, унаследованный от класса Menu).
Как и в случае с одиночным наследованием, при использовании множественного
наследования важно иметь в виду условие «быть экземпляром». В нашем примере
множественное наследование оправдано, поскольку имеет смысл каждое из утверждений:
«подменю есть меню» и «подменю есть пункт меню». Когда отношение «быть
экземпляром» не выполнено, множественное наследование может использоваться
неправильно. Например, неверно описывать «автомобиль» как подкласс двух классов:
«мотор» и «корпус». Точно так же класс «яблочный пирог» не следует выводить из
классов «пирог» и «яблоко». Очевидно, что яблочный пирог есть пирог, но он не есть
яблоко.
Когда множественное наследование используется правильным образом, происходит
тонкое, но тем не менее важное изменение во взгляде на наследование. Интерпретация
условия «быть экземпляром» при одиночном наследовании рассматривает подкласс как
специализированную форму другой категории (родительского класса). При
множественном наследовании класс является комбинацией нескольких различных
характеристик со своими интерфейсами, состояниями и базовым поведением,
специализированным для рассматриваемого случая. Операции записи и поиска объектов в
неком хранилище (скажем, на жестком диске) представляют типичный пример. Часто эти
действия реализованы как часть поведения, связанного с определенным классом типа
Persistence или Storable. Чтобы придать такую способность произвольному классу, мы
просто добавляем Storable к списку предков класса.
Конечно же, важно различать между наследованием от независимых источников и
построением из независимых компонент, что иллюстрируется на примере «автомобиля» и
«мотора».
13.3. Двусмысленность имен
Часто возникающее затруднение при множественном наследовании состоит в том, что
имена могут использоваться для обозначения более чем одной операции. Чтобы
проиллюстрировать это, мы рассмотрим еще раз модель карточной игры. Предположим,
что уже имеется абстракция карточной колоды CardDeck, которая обеспечивает
надлежащую функциональность: тасование колоды (метод shuffle), выбор отдельной
карты (метод draw
1
)
1
Здесь используется тот факт, что в английском языке глагол draw кроме значения «рисовать» имеет также и
массу других значений (в англо-русском словаре В. К. Мюллера их список занимает почти две колонки
текста), среди которых имеются «отбрасывать», «вытаскивать» и «тянуть жребий». — Примеч. перев. и
PDF created with pdfFactory Pro trial version www.pdffactory.com