Реферат на тему Агрегация или наследование
Работа добавлена на сайт bukvasha.net: 2015-06-29Поможем написать учебную работу
Если у вас возникли сложности с курсовой, контрольной, дипломной, рефератом, отчетом по практике, научно-исследовательской и любой другой работой - мы готовы помочь.
Вид различия | Агрегация | Наследование |
1. Добавление новых полей | Скорее всего, поскольку следует различать состояния объекта как старого класса и как нового класса | Необязательно, поскольку функциональность может быть реализована скорее всего путем переопределения виртуальных функций. |
2. Переопределение виртуальных функций | Нет смысла | Скорее всего |
3. Сохранение работоспособности имеющегося кода | Сомнительно. Имеющийся код будет заводить объекты уже модифицированного класса, а модификация проводилась в целях иной задачи | Безусловно. Изменения не касаются имеющегося кода. |
4. Достижимость поставленной цели | Да, скорее всего. | Да, скорее всего. |
5. Наличие особых требований к имеющемуся классу | Нет. Если чего-то в нем не хватает, то это будет дописано. | Да. Базовый класс должен предусмотреть возможность наследования (например, объявить виртуальный деструктор) и предоставить часть своих функций как виртуальные. |
6. Наличие особого внимания к имеющемуся коду класса, контрольные точки | Да. Этапы инициализации и деинициализации объектов должны быть проконтролированы обязательно. Желательно с целью сохранения совместимости с предыдущим поведением объекта. | Нет, если средства реализации поддерживают автоматический вызов конструкторов и виртуальных деструкторов и да, если не поддерживают. |
7. Наличие особых требований к предыдущему оперативному окружению на этапе создания / удаления объекта | Никаких. Для окружения ничего не меняется. | Да. Точки создания объектов должны быть проверены на предмет создания объектов именно требуемого класса. |
8. Наличие особых требований к предыдущему оперативному окружению на этапе жизни объекта | Возможно, если модифицированный код меняет свое отношение к внешнему контексту. | Никаких. Предыдущее оперативное окружение видит и должно видеть базовый класс. Если переопределяемые виртуальные функции меняют отношение объекта к контексту, могут быть проблемы. |
9. Разрастание кода имеющейся программы | Да, безусловно. | Нет. |
Эти пункты следует учитывать, если проектирование классов производится не спонтанно и "по живому", а более-менее ответственно. Тем более, что всегда есть время обдумать свои шаги.
Приведем реальные примеры, когда перед программистом может стоять выбор способа реализации задачи - использовать агрегирование или наследование и что будет удобнее. Вопрос в направлении действия слова "удобнее" рассматривается в основном в контексте удобства использования, поскольку качество используемого кода определяется именно удобством его использования, а не удобствами, которые испытывал программист при его написании.
Рассмотрим окна в Windows. Окна в Windows все и независимо ни от чего имеют две основные характеристики - это определение класса и функция обработки сообщений. Это пункт номер раз, который известен всем. Кроме того, окна в Windows имеют четкую градацию на 4 вида, различающиеся как деталями функции обработки сообщений, так и отношением к нему системы управления окнами. Это различие определяется функциями по умолчанию, которые вызывает программист, если не обрабатывает какие-либо сообщения. Это функции DefWindowProc, DefDialogProc, DefMDIChildProc и DefMDIFrameProc.
Классика объектно-ориентированного проектирования подсказывает, что следует, как минимум, в графе наследований предусмотреть класс, реализующий абстрактное окно. С тем, чтобы впоследствии этот класс можно было использовать для написания как этих 4 видов окон, так и произвольных контролов. В отношении уточнения поведения окна конкретного вида граф наследований класса окна в данном случае особой роли не играет. Стоит вопрос - что выбрать для реализации этих четырех видов окон - агрегацию или наследование с получением еще четырех или более классов?
Реализация как библиотек OWL, так и MFC выбрала путь наследования. Получены дополнительные классы, реализующие соответственно просто несущее окно, контрол частного вида, диалоговое окно, MDI Frame и MDI Child (как сказал Пушкин, "... прости, не знаю как перевести."). В результате для использования каждого из этих классов требуется кроме общего кода, оперирующего базовым классом окна, частный код, который может быть вызван только для конкретных классов. Там, где используется специфика MDI Child, может быть использован код только для него. Более того - код вызова, специфичный для MDI Child, может пользоваться только этим классом либо его наследником.
Программист, использующий библиотеку классов, обязывается при написании программы создавать наследника соответствующего класса. Таким образом, специфика окон в таком подходе проектирования вылилась не только в код специальных классов, но и в существование кода, применимого только к данному классу и существующему вне его. Этот факт нашел отражение в том, что встроенные в среды разработки Borland C++ и Visual C++ мастера классов при попытке создания новых классов, несущих прикладную нагрузку, обязательно требуют указания базового класса и последующую модификацию производят с учетом этих нескольких базовых классов. Унифицированный визуальный дизайнер окон в таком подходе представляет собой либо практически нереализуемую задачу, либо является объединением 4-х дизайнеров. В обеих этих средах более-менее полноценно реализовано визуальное редактирование лишь для диалоговых окон.
Реализация библиотеки VCL пошла по пути агрегирования возможностей и написания базового класса (TForm) таким образом, что различие между 4-мя видами окон сводится к указанию значения поля данных. При этом код класса содержит код для всех 4-х видов окон. Становится невозможно построение программы, использующей только диалоговые окна и не содержащей кода поддержки MDI. При этом становится возможным использование единого визуального дизайнера для редактирования всех 4-х видов окон. Наличие же функций поддержки MDI у объекта, используемого в качестве диалогового окна мало кого смущает, поскольку эти функции для диалоговых окон просто не приходит в голову вызывать. Более того - у программиста есть уверенность, что то, что он проектирует в качестве обычного всплывающего окна в интерфейсе SDI, всегда можно будет использовать так же в качестве и MDI Frame, и MDI Child, и простого модального диалога. Более того, сохраняется возможность менять показ этого окна в зависимости от состояния программы. Например, в некоторых случаях MDI Child должен быть показан в модальном режиме.
Плюсы и минусы выбора графа наследования видны уже на конкретном приведенном примере. В иных случаях следует определяться с выбором, исходя из конкретной задачи. Что интересно, на выбор может оказать влияние знание не только имеющейся задачи и ее целей, но и последующих перспектив работы. Как показала практика применения вышеупомянутых средств разработки, то есть Borland C++ / Visual C++ и Delphi / Borland C++ Builder, на первых двух успешно строились долго разрабатываемые проекты, сложные по своей организации и слабо зависящие от разнообразия интерфейса (либо получившиеся такими). Вторые два средства использовались для действительно быстрой и качественной разработки проектов, имеющих развитые интерфейсные средства. Сама возможность применения единого визуального дизайнера окон явилась своего рода катализатором возникновения сообщества компонентного подхода.
Следует отметить, что компонентный подход, применяемый в Visual C++ (ActiveX), зарекомендовал себя в качестве более инертного. Наследование дельфийских компонентов и переопределение их виртуальных функций выполнить гораздо проще, чем наследование и переопределение виртуальных функций ActiveX контролов. Второе выполнить просто невозможно, поскольку следует наследовать не класс с частично реализованным поведением, а интерфейсы, вообще не содержащие реализации. Если эмулировать поведение похожего класса еще возможно, то слегка переопределить его виртуальные функции - уже большая спортивная проблема. Остается разве что полностью инкапсулировать исходный класс в качестве свойства и полностью переопределять реализуемые им интерфейсы делегируя их реализацию этому инкапсулированному объекту.
Какие выводы / рекомендации можно сделать для себя? Если нужно сделать несколько объектов, которые различаются несущественно и имеют массу общего, то хорошим выбором может быть использование агрегирования свойств и изменения необходимых функций с учетом их состояния. Если архитектура программы по своему строению использует массу объектов, которые видны этой архитектуре совершенно безотносительно свойств, которыми они различаются, и методы, которыми эти объекты различаются, существенны, то скорее всего следует выбрать путь наследования.
Как можно определить, что отнести в добавляемым свойствам при агрегировании, а что к добавляемым / переопределяемым при наследовании?
Возьмем и нарисуем квадратики / кружки, соответствующие различным понятиям. В них выделим свойства и методы. После чего сгруппируем те квадратики, которые имеют общие свойства и методы. Это пересечение свойств / методов следует выделить в новый квадратик и назвать его базовым, а те квадратики, которые объединялись, назвать наследниками этого базового. И оставить в качестве дополнения к наследованным то, что не попало в пересечение. Теперь стоит вопрос - какой путь выбрать? Если оставить все как есть, то получится вариант проектирования новых классов путем наследования. Если интересует вопрос получения новых классов путем агрегирования, то квадратики следует объединить в один или более. В полученный укрупненный квадратик каждый из вошедших в него внесет что-то свое.
Следует посмотреть, можно ли часть свойств находящихся в объединении, но не находящихся в пересечении, объединить в одно или более. Являются ли они взаимоисключающими? Могут ли они представлять состояние объекта получаемого класса? Если да, то вероятность успешной агрегации весьма высока. Наличие в получаемом классе свойств и методов, возможность использования которых может зависеть не от класса объекта, а от состояния других свойств, не должна пугать. Просто надо корректно написать код обработки и в одних случаях игнорировать неправильные обращения, в других генерировать ошибку. Например, в библиотеке VCL для формы можно одновременно указать стиль заголовка и его отсутствие. Этот парадокс решается просто - если окно без заголовка, то стиль заголовка игнорируется.
При этом не следует выбирать для себя только один стиль проектирования на всю жизнь и только его и придерживаться. Это может нанести ущерб в ситуации, когда было бы лучше выбрать другой. Просто ими обоими надо владеть.