Джим Арлоу, Айла Нейштадт - UML 2 и Унифицированный процесс. Практический объектно-ориентированный анализ и проектирование (1037782), страница 70
Текст из файла (страница 70)
Наследование379чить отношения между классами и максимально сократить связанность.Объектная модель с большим количеством взаимосвязей эквивалентна «спагеттикоду» в неОО мире. Она приведет к созданию малопонятной и неудобной в эксплуатации системы. Вы увидите, что ОО системы с большим количеством взаимосвязей часто возникают в проектах, в которых нет формального процесса моделирования, а системапросто эволюционирует во времени произвольным образом.Неопытные проектировщики должны быть внимательными и не устанавливать связи между классами просто потому, что в одном из нихесть код, который может использоваться другим. Это самый плохойвариант повторного использования, когда ради небольшой экономиивремени разработки в жертву приносится архитектурная целостностьсистемы.
Все ассоциации между классами должны тщательно продумываться. Многие ассоциации перейдут в проектную модель непосредственно из аналитической, но целый ряд ассоциаций вводится иззаограничений реализации или желания повторно использовать код. Этиассоциации необходимо проверять наиболее внимательно.Конечно, некоторая степень связанности хороша и необходима. Допускается высокая связанность в рамках подсистемы, поскольку этоуказывает на высокую связность компонента. Подрывают архитектуру только многочисленные связи между подсистемами.
Необходимоактивно стремиться к сокращению подобной связанности.17.6. НаследованиеПри проектировании наследование играет намного более важнуюроль, чем при анализе. В анализе наследование использовалось, толь+ко если между классами анализа имело место четкое и явно выраженное отношение «является». Однако при проектировании наследованиеможет применяться в тактических целях для повторного использования кода. Это совсем иная стратегия, поскольку наследование фактически используется для упрощения реализации дочернего класса, а недля выражения бизнесотношения между родителем и потомком.В следующих нескольких разделах рассматриваются стратегии эффективного использования наследования в проектировании.17.6.1.
Сравнение агрегации и наследованияНаследование – очень мощный метод. Оно является ключевым механизмом формирования полиморфизма в строго типизированных языках программирования, таких как Java, C# и C++. Однако неопытныеОО проектировщики и программисты часто используют его неправильно. Необходимо осознавать, что наследование имеет определенные нежелательные характеристики.380Глава 17. Проектные классыНаследование – самая строгая форма связанности классов. Это жесткоеотношение.•Это самая строгая из возможных форма связанности двух или болееклассов.•В иерархии классов инкапсуляция низкая. Изменения базовогокласса передаются вниз по иерархии и приводят к изменениям подклассов.
Это явление называют проблемой «хрупкости базовогокласса», когда изменения базового класса имеют огромное влияниена другие классы системы.•Это очень жесткий тип отношения. Во всех широко используемыхОО языках программирования отношения наследования постоянныво время выполнения. Создавая и уничтожая отношения во времявыполнения, можно изменять иерархии агрегации и композиции,но иерархии наследования остаются неизменными.
Это делает наследование самым жестким типом отношений между классами.Система на рис. 17.6 – типичный пример решения задачи моделирования ролей в организации, выполненного неопытным разработчиком.На первый взгляд все вполне приемлемо, однако здесь есть проблемы.Рассмотрим предложенный вариант. Объект john типа Programmer (программист) необходимо повысить до типа Manager (менеджер).
Вамдолжно быть понятно, что изменить класс Джона (john) во время выполнения нельзя. Поэтому единственный способ повысить Джона –создать новый объект Manager (названный john:Manager), скопироватьв него из объекта john:Programmer все существующие данные и затемудалить john:Programmer для сохранения целостности приложения. Конечно, это очень сложно и совершенно не соответствует тому, как всепроисходит на самом деле.В сущности, в модели на рис. 17.6 допущена фундаментальная семантическая ошибка. Служащий (Employee) – это именно должность (Job) илиэто указывает на то, что служащий занимает некоторую должность?Ответ на этот вопрос приводит к решению проблемы (см. рис. 17.7).EmployeeManagerProgrammer«instantiate»john:ProgrammerРис.
17.6. Неверная модель38117.6. Наследование0..*0..*JobEmployee«instantiate»ManagerProgrammer«instantiate»«instantiate»:Manager:Programmerjohn:Employeeчтобы повысить Джона, просто изменяемэту связь во время выполненияРис. 17.7. Верная модельПри использовании агрегации получаем верную семантику – у служащего (Employee) есть должность (Job).
При такой более гибкой моделиу служащих при необходимости может быть несколько должностей.Подклассы всегда должны представлять «особую разновидность чеголибо», а не «выполняемую роль».Путем замены наследования агрегацией получена более гибкая и семантически правильная модель.
Здесь представлен важный общийпринцип: подклассы всегда должны «являться разновидностью чеголибо», а не «являться ролью, исполняемой чемлибо». Если рассматривается бизнессемантика компаний, служащих и должностей, очевидно, что должность – это роль, исполняемая служащим, и она на самом деле не указывает на разновидность служащего. Таким образом,наследование – безусловно, неверный выбор для моделирования такого рода бизнесотношения. С другой стороны, в компании много раз+ных должностей. Это указывает на то, что иерархия наследованиядолжностей (корнем которой является абстрактный базовый классJob), вероятно, является хорошей моделью.17.6.2. Множественное наследованиеПри множественном наследовании у класса может быть более одногородителя.Иногда возникает потребность наследования от нескольких родителей.
Такое наследование называют множественным. Его поддерживают не все ОО языки программирования, например в Java и C# допускается только единичное наследование. На практике отсутствие поддержки множественного наследования не является проблемой, по382Глава 17. Проектные классыскольку его всегда можно заменить единичным наследованием иделегированием. Даже несмотря на то, что иногда множественное наследование предлагает более элегантное решение задачиы проектирования, оно может использоваться, только если целевой язык реализации его поддерживает.О множественном наследовании важно знать следующее.Родители должны быть семантически не связанными.•••Все участвующие родительские классы должны быть семантическине связанными.
В случае наличия какоголибо совмещения в семантике базовых классов между ними возможны непредвиденные взаимодействия. Это может привести к необычному поведению подкласса. Мы говорим, что базовые классы должны быть ортогональными(расположенными под прямым углом друг относительно друга).Между подклассом и всеми его суперклассами должны действоватьпринцип замещаемости и принцип «является разновидностью».У суперклассов не должно быть общих родителей.
В противном случае в иерархии наследования образуется цикл и возникает множество путей наследования одних и тех же возможностей от более абстрактных классов. У языков программирования, поддерживающихмножественное наследование (таких как С++), есть специальные,особые для каждого языка способы разрешения циклов в иерархиинаследования.Смешанные классы создаются, чтобы путем множественного наследования быть «смешанными» с другими классами.
Это надежное и мощноесредство.Один из общепринятых способов эффективного использования множественного наследования – «смешанный» (mixin) класс. Эти классы неявляются понастоящему автономными классами. Они разрабатываются специально для того, чтобы быть «смешанными» с другими классами с помощью наследования. На рис. 17.8 класс Dialer (набирательномера) – простой смешанный класс. Его единственная функция –набор телефонного номера.
То есть сам по себе он не представляет особой ценности, но предоставляет связный пакет полезного поведения,AlarmDialerAlarmDialerРис. 17.8. Смешанный класс Dialer17.7. Шаблоны383которое может широко использоваться другими классами посредствоммножественного наследования. Этот смешанный класс – пример обычного утилитного класса, который мог бы стать частью многократно используемой библиотеки.17.6.3.