Эта публикация — ответ на текст «Решетчатое наследование», опубликованный хабрапользователем potan, с предложением альтернативы, на субъективный взгляд автора, более близкой к ООП с классами.
«Целью наследование является привязка кода (набора методов) к минимальному набору свойств сущности (как правило — объекта), которые он обеспечивает и которые ему требуются».
Разве это так? К тому же в чём причина привязки? По каким признакам мы делаем вывод, что вот эти методы с этим объектом связать, а вот какие-то другие сделать частью других объектов, которые будут брать недостающие свойства/параметры через публичные интерфейсы?
Я придерживаюсь SOLID и идеи единственной ответственности на объект. Следовательно, иерархии наследования в таком случае — это уточнения ответственности к конкретным обстоятельствам, в которых ей надо исполняться.
Однако, объекты реального мира (либо предметной области) могут нести в себе несколько разных функций/ответственностей, поэтому прямого отображения их в систему классов может не получится. Такой объект в системе классов приложения я вижу как набор/контейнер из ответственностей, поддерживаемых объектом предметной области. Задача такого класса — ответственности между собой согласовывать. Согласование частично происходит через специфических потомков ответственностей, частично внутри класса контейнере.
Постараюсь привести пример близкий к примеру статьи.
Умения
- умение стрелять
- умение стрелять из ПЗРК
- умение плавать
- умение ходить
Умение — предоставляет общую информацию, например, связь с деревом умений и слот для предметов умения.
Умение стрелять — предоставляет возможность целиться, в слоте умений должно быть стреляющее оружие.
Умение стрелять из ПЗРК — уточняет реализацию функции целиться, например, экран ПЗРК рисует.
Умение плавать — позволяет погружаться в воду (например, не умирая при этом) и показывает шкалу времени, персонаж как-бы летает в воде.
Кстати, вот тут я пропустил абстракцию — умение передвигаться, от которой унаследовать можно умения плавать и ходить.
Тогда конкретные персонажи будут содержать наборы умений.
Персонаж:
- текущее умение передвигаться;
- список умений: [].
Пехотинец (Персонаж):
- текущее умение стрелять;
- список умений: [плавать, ходить, стрелять из ПЗРК].
Утка (Персонаж):
- список умений: [плавать].
Ответственность персонажа — согласовывать между собой его умения и переключать их. Например, когда персонаж в воду заходит. Если есть умение плавать — активировать его, иначе — прорисовывать картинку персонаж тонет.
Вопрос: что же будет, если данная набор будет разрастаться?
А как он может разрастаться?
- добавить новое умение;
- добавить нового персонажа;
- добавить существующему персонажу умение, которое ранее ему было не свойственно (научить утку стрелять из ПЗРК).
Новое умение:
.Eсли это количественные вещи, например, ПЗРК стрелял с вероятностью попадания 0.6, а теперь 0.8 — так это просто разные записи в бд одного и того-же класса.
.Если что-то новое, например, меч — тогда в любом случае нужна гора кода, которая в начале владение мечом абстрактно опишет, а потом у каждого персонажа свои детали добавит. Хотя не обязательно сложная модификация (если абстракции Персонажа и его наследников хороши).
Новый класс персонажей — тут нужно описывать сочетания умений, как их согласовать друг с другом. Код как у подкласса персонажа возможно выделение отдельных подклассов умений, приспособленных под нового персонажа.
Сложнее существующему добавить ещё что-то, т.к. нужно внутренний код существующего перебирать заново. И возможно, проще будет унаследовать потомка.
Например, Утка нас всем устраивает, только вот стрелять не умеет. Значит, этот функционал надо наложить, может удобнее будет даже сразу 2 класса добавить: Простая Утка и Утка, умеющая стрелять. А в классе Утка оставить общие для дочерних уток ответственности — умение плавать — приспособленное к утке: в виде держаться на воде и нырять.
Ещё одной стороной сложности добавить новую ответственность (класс). Есть необходимость соотнести её со всеми уже существующими. Потенциально этот процесс может уйти в бесконечность, и чем больше уже существующих классов, тем он дольше. Помощью в этом есть проектирование сверху вниз: есть мир, он создаёт персонажей и позволяет им взаимодействовать по средствам умений. Персонажи умения могут приобретать/терять.
С такой позиции mixin кажется недовыделенными классом. Недовыделенным его оставили из-за стремления сделать быстрее и не перестраивать существующие иерархии классов и их композиции.
Автор: steb