Наследование – один из самых противоречивых механизмов ООП. Чем больше мы узнаем о нем, тем больше подводных камней встречается. Мало того, что оно добавляет в код невероятное количество новых понятий и особенностей поведения, так оно еще имеет фундаментальные изъяны. И если с первым все более менее понятно — на протяжении всех предыдущих уроков мы только и занимались тем, что переосмысливали работу с классами, то со вторым нужно разобраться.
Ключевая проблема с иерархиями в том, что наш мир не иерархичен. Любая классификация всегда опирается на конкретный признак, который интересует нас в конкретный момент времени. И эта же классификация становится бесполезной, если берется другой признак. Это хорошо видно в интернет-магазинах, у которых навороченные фильтры для выбора товара: группировка по производителю, по применимости, по безопасности для детей и так далее. В каждой конкретной ситуации будет своя структура.
Возьмем понятие User. Статьи по наследованию часто любят показывать иерархии пользователей создавая у разработчиков уверенность, что мир так и устроен. Давайте попробуем прикинуть по каким признакам можно построить иерархию пользователей:
Все это может и будет встречаться в рамках даже одной программы. В зависимости от того, какую задачу мы решаем, может понадобиться разное представление. Наследование не дает такой свободы, наоборот, оно гвоздями приколачивает нас к конкретной структуре, которую уже не поменять. Единственным выходом в рамках этой парадигмы становится еще большее число наследований. В итоге у нас будет комбинация всех возможных поведений, которые встретятся в программе, а это иерархии с десятками и сотнями классов. Не забудьте, что все это каким-то образом должно согласовываться с интерфейсами, которые тоже могут расширять друг друга.
Выходом могло бы быть множественное наследование, но, как показала жизнь некоторых языков (C++), множественное наследование делает все еще сложнее. Поэтому от него отказались все, кто только могли.
В конечном итоге, у разработчиков сформировалась общая позиция по отношению к наследованию, которая звучит так: композиция вместо наследования. Если попробовать загуглить эту фразу, то поисковик покажет невероятное количество статей по этой теме. Этот подход мы уже изучали в курсе "JS: Полиморфизм". Он сводится к более грамотному разделению зон ответственности в приложении, делегированию функциональности другим объектам, нужным в конкретных ситуациях.
С этого момента начинаются сложности. В большинстве статей, посвященных этому вопросу, приводятся либо ошибочные, либо слишком искусственные примеры, которые не дают особого понимания. Для начала, отделим две разные причины использования наследования. Одна из них связана с прямым назначением наследования, другая вытекает из неверного понимания принципов организации кода.
Яркий пример использования наследования не по назначению, это смешивание разных уровней абстракции. Выше был пример про пользователей разделяемых по типу хранилища — SQLUser. Возникает вопрос: как пользователь, с точки зрения нашей предметной области, связан с техническими аспектами хранения этих пользователей? Никак не связан, такой код в принципе не должен существовать и наследование для него не предназначено.
Такой код появляется не от незнания ООП, а от непонимания общих принципов организации кода, построения абстракций. Эта тема не является специфичной для ООП, но ООП делает ее сложнее из-за большого числа новых сущностей, которое оно вводит. Обязательно прочитайте эту статью, которая проливает свет на архитектуру.
Парадокс состоит в том, что фраза "composition over inheritance" (композиция вместо наследования) относится именно к такому использованию наследования. То есть проблема не в наследовании как таковом, а в том, что оно оказалось удобным способом организации кода для тех, кто не очень хорошо знает, как его организовывать, что такое барьеры абстракции и слои приложения.
Необходимость наследования классов возникает там, где классы связаны общим кодом (это не отношение подтипов). В такой ситуации нужна какая-то альтернатива наследованию.
Решение этой проблемы известно довольно давно и называется миксины. Миксины – настоящая альтернатива правильному использованию наследования.
Вам ответят команда поддержки Хекслета или другие студенты.
Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.
Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно
Наши выпускники работают в компаниях:
С нуля до разработчика. Возвращаем деньги, если не удалось найти работу.
Зарегистрируйтесь или войдите в свой аккаунт