При проектировании игры возник вопрос. Предположим, имеется интерфейс IUnit и его имплементируют все классы, описывающие юнитов. Но у этих классов часть кода может быть одинакова, следовательно, или придется дублировать код, что просто противоречит назначению функций, или добавить поведение по умолчанию.
Я решил сделать так: интерфейс остаётся, но добавляется абстрактный класс UnitBase, в котором реализуется поведение по умолчанию для некоторых методов, а уже классы, описывающие юнитов, или явно имплементируют интерфейс, если они совсем не похожи на UnitBase, или наследуют UnitBase, чтобы избежать дублирования кода.
Однако, существует мнение, что наследование нарушает инкапсуляцию, следовательно наследование абстрактного класса - есть плохо и мой подход не годится. Так ли это и насколько данная архитектура соответствует канонам ООП?
Ответ
Предложенный подход вполне состоятелен. Желание исключить дублирование - похвально и должно поощряться.
По большому счёту, если поведение по умолчанию, которое будет в UnitBase может не подойти для некоторых юнитов, то, вероятно, название не должно быть UnitBase, а быть более специфичным, например, GroundUnit, AirUnit и т.п.
Вообще, в силу того, что в С++ нет истинных интерфейсов (как в Java или C#), то разделение IUnit и UnitBase при условии соблюдения смысла предыдущего параграфа в принципе не обязательно. Достаточно иметь хотя бы одну чисто виртуальную функцию в классе, чтобы запретить его инстанцирование без реализации явного наследника.
существует мнение, что наследование нарушает инкапсуляцию
Странное мнение, хорошо бы узнать, где Вы его услышали. Инкапсуляция - это сокрытие данных и предоставление к ним доступа через публичные функции, объединённые с этими данными в один каркас (класс). Если сокрытие реализовано правильно, использование публичных функций не нарушает инвариант класса, и публичное наследование классов придерживается принципа "объект производного класс является объектом базового", то никаких нарушений инкапсуляции быть не может.
Другое дело, что наследование делает архитектуру более жесткой, если класс обозначен как наследник другого класса, эту связь уже нельзя изменить в динамике. Поэтому часто можно услышать предложения замены наследования агрегацией. Т.е. добавить в класс поле типа IBase* и уже можно будет менять этот указатель в рантайме, обеспечивая различное поведение, в зависимости от конкретного инстанцированного класса.
Комментариев нет:
Отправить комментарий