0 added
0 removed
Original
2026-01-01
Modified
2026-02-26
1
<p>Наследование - один из самых противоречивых механизмов ООП. Чем больше мы узнаем о нём, тем больше подводных камней встречается. Мало того, что оно добавляет в код невероятное количество новых понятий и особенностей поведения, так оно ещё имеет фундаментальные изъяны. И если с первым всё более менее понятно - на протяжении всех предыдущих уроков мы только и занимались тем, что переосмысливали работу с классами, то со вторым нужно разобраться.</p>
1
<p>Наследование - один из самых противоречивых механизмов ООП. Чем больше мы узнаем о нём, тем больше подводных камней встречается. Мало того, что оно добавляет в код невероятное количество новых понятий и особенностей поведения, так оно ещё имеет фундаментальные изъяны. И если с первым всё более менее понятно - на протяжении всех предыдущих уроков мы только и занимались тем, что переосмысливали работу с классами, то со вторым нужно разобраться.</p>
2
<p>Ключевая проблема с иерархиями в том, что наш мир не иерархичен. Любая классификация всегда опирается на конкретный признак, который интересует нас в конкретный момент времени. И эта же классификация становится бесполезной, если берётся другой признак. Это хорошо видно в интернет-магазинах, у которых навороченные фильтры для выбора товара: группировка по производителю, по применимости, по безопасности для детей и так далее. В каждой конкретной ситуации будет своя структура.</p>
2
<p>Ключевая проблема с иерархиями в том, что наш мир не иерархичен. Любая классификация всегда опирается на конкретный признак, который интересует нас в конкретный момент времени. И эта же классификация становится бесполезной, если берётся другой признак. Это хорошо видно в интернет-магазинах, у которых навороченные фильтры для выбора товара: группировка по производителю, по применимости, по безопасности для детей и так далее. В каждой конкретной ситуации будет своя структура.</p>
3
<p>Возьмём понятие<em>User</em>. Статьи по наследованию часто любят показывать иерархии пользователей создавая у разработчиков уверенность, что мир так и устроен. Давайте попробуем прикинуть по каким признакам можно построить иерархию пользователей:</p>
3
<p>Возьмём понятие<em>User</em>. Статьи по наследованию часто любят показывать иерархии пользователей создавая у разработчиков уверенность, что мир так и устроен. Давайте попробуем прикинуть по каким признакам можно построить иерархию пользователей:</p>
4
<ul><li>По полу (MaleUser, FemaleUser)</li>
4
<ul><li>По полу (MaleUser, FemaleUser)</li>
5
<li>На основе аутентификации (User, Guest)</li>
5
<li>На основе аутентификации (User, Guest)</li>
6
<li>По роли (Admin, Member)</li>
6
<li>По роли (Admin, Member)</li>
7
<li>По типу должности (Marketer, SalesManager, Programmer, Tester, Player)</li>
7
<li>По типу должности (Marketer, SalesManager, Programmer, Tester, Player)</li>
8
<li>По принадлежности к какой-либо группе (UserFromRussia, UserWhoLikesSpartak)</li>
8
<li>По принадлежности к какой-либо группе (UserFromRussia, UserWhoLikesSpartak)</li>
9
<li>По источнику (UserFromFacebook, UserFromGithub)</li>
9
<li>По источнику (UserFromFacebook, UserFromGithub)</li>
10
<li>По типу хранилища (SQLUser, LocalStorageUser)</li>
10
<li>По типу хранилища (SQLUser, LocalStorageUser)</li>
11
<li>...</li>
11
<li>...</li>
12
</ul><p>Всё это может и будет встречаться в рамках даже одной программы. В зависимости от того, какую задачу мы решаем, может понадобиться разное представление. Наследование не даёт такой свободы, наоборот, оно гвоздями приколачивает нас к конкретной структуре, которую уже не поменять. Единственным выходом в рамках этой парадигмы становится ещё большее число наследований. В итоге у нас будет комбинация всех возможных поведений, которые встретятся в программе, а это иерархии с десятками и сотнями классов. Не забудьте, что всё это каким-то образом должно согласовываться с интерфейсами, которые тоже могут расширять друг друга.</p>
12
</ul><p>Всё это может и будет встречаться в рамках даже одной программы. В зависимости от того, какую задачу мы решаем, может понадобиться разное представление. Наследование не даёт такой свободы, наоборот, оно гвоздями приколачивает нас к конкретной структуре, которую уже не поменять. Единственным выходом в рамках этой парадигмы становится ещё большее число наследований. В итоге у нас будет комбинация всех возможных поведений, которые встретятся в программе, а это иерархии с десятками и сотнями классов. Не забудьте, что всё это каким-то образом должно согласовываться с интерфейсами, которые тоже могут расширять друг друга.</p>
13
<p>Выходом могло бы быть множественное наследование, но множественное наследование делает все ещё сложнее, как показывает опыт (C++) и других языков. Поэтому от него отказались все, кто только могли.</p>
13
<p>Выходом могло бы быть множественное наследование, но множественное наследование делает все ещё сложнее, как показывает опыт (C++) и других языков. Поэтому от него отказались все, кто только могли.</p>
14
<p>В конечном итоге, у разработчиков сформировалась общая позиция по отношению к наследованию, которая звучит так: композиция вместо наследования. Если попробовать загуглить эту фразу, то поисковик покажет невероятное количество статей по этой теме. Этот подход мы уже изучали в курсе "PHP: Полиморфизм". Он сводится к более грамотному разделению зон ответственности в приложении, делегированию функциональности другим объектам, нужным в конкретных ситуациях.</p>
14
<p>В конечном итоге, у разработчиков сформировалась общая позиция по отношению к наследованию, которая звучит так: композиция вместо наследования. Если попробовать загуглить эту фразу, то поисковик покажет невероятное количество статей по этой теме. Этот подход мы уже изучали в курсе "PHP: Полиморфизм". Он сводится к более грамотному разделению зон ответственности в приложении, делегированию функциональности другим объектам, нужным в конкретных ситуациях.</p>
15
<p>С этого момента начинаются сложности. В большинстве статей, посвящённых этому вопросу, приводятся либо ошибочные, либо слишком искусственные примеры, которые не дают особого понимания. Для начала, отделим две разные причины использования наследования. Одна из них связана с прямым назначением наследования, другая вытекает из неверного понимания принципов организации кода.</p>
15
<p>С этого момента начинаются сложности. В большинстве статей, посвящённых этому вопросу, приводятся либо ошибочные, либо слишком искусственные примеры, которые не дают особого понимания. Для начала, отделим две разные причины использования наследования. Одна из них связана с прямым назначением наследования, другая вытекает из неверного понимания принципов организации кода.</p>
16
<h2>Использование не по назначению</h2>
16
<h2>Использование не по назначению</h2>
17
<p>Яркий пример использования наследования не по назначению, это смешивание разных уровней абстракции. Выше был пример про пользователей разделяемых по типу хранилища -<em>SQLUser</em>. Возникает вопрос: как пользователь, с точки зрения нашей предметной области, связан с техническими аспектами хранения этих пользователей? Никак не связан, такой код в принципе не должен существовать и наследование для него не предназначено.</p>
17
<p>Яркий пример использования наследования не по назначению, это смешивание разных уровней абстракции. Выше был пример про пользователей разделяемых по типу хранилища -<em>SQLUser</em>. Возникает вопрос: как пользователь, с точки зрения нашей предметной области, связан с техническими аспектами хранения этих пользователей? Никак не связан, такой код в принципе не должен существовать и наследование для него не предназначено.</p>
18
<p>Такой код появляется не от незнания ООП, а от непонимания общих принципов организации кода, построения абстракций. Эта тема не является специфичной для ООП, но ООП делает её сложнее из-за большого числа новых сущностей, которое оно вводит. Обязательно прочитайте<a>эту статью</a>, которая проливает свет на архитектуру.</p>
18
<p>Такой код появляется не от незнания ООП, а от непонимания общих принципов организации кода, построения абстракций. Эта тема не является специфичной для ООП, но ООП делает её сложнее из-за большого числа новых сущностей, которое оно вводит. Обязательно прочитайте<a>эту статью</a>, которая проливает свет на архитектуру.</p>
19
<p>Парадокс состоит в том, что фраза "composition over inheritance" (композиция вместо наследования) относится именно к такому использованию наследования. То есть проблема не в наследовании как таковом, а в том, что оно оказалось удобным способом организации кода для тех, кто не очень хорошо знает, как его организовывать, что такое барьеры абстракции и слои приложения.</p>
19
<p>Парадокс состоит в том, что фраза "composition over inheritance" (композиция вместо наследования) относится именно к такому использованию наследования. То есть проблема не в наследовании как таковом, а в том, что оно оказалось удобным способом организации кода для тех, кто не очень хорошо знает, как его организовывать, что такое барьеры абстракции и слои приложения.</p>
20
<h2>Использование по назначению</h2>
20
<h2>Использование по назначению</h2>
21
<p>Необходимость наследования классов возникает там, где классы связаны общим кодом (это не отношение подтипов). В такой ситуации нужна какая-то альтернатива наследованию. И здесь появляются варианты.</p>
21
<p>Необходимость наследования классов возникает там, где классы связаны общим кодом (это не отношение подтипов). В такой ситуации нужна какая-то альтернатива наследованию. И здесь появляются варианты.</p>
22
<p>В самом простом случае, общий код не публичный. Тогда хватит обычной функции, которую эти классы будут использовать внутри себя. А если общий код был публичным? Большинство руководств рекомендуют создать соответствующий интерфейс и реализовать его в каждом из классов. Основной недостаток такого подхода - дублирование кода в каждом классе. То есть мы пришли к тому, от чего пытались уйти.</p>
22
<p>В самом простом случае, общий код не публичный. Тогда хватит обычной функции, которую эти классы будут использовать внутри себя. А если общий код был публичным? Большинство руководств рекомендуют создать соответствующий интерфейс и реализовать его в каждом из классов. Основной недостаток такого подхода - дублирование кода в каждом классе. То есть мы пришли к тому, от чего пытались уйти.</p>
23
<p>Решение этой проблемы известно довольно давно и называется<a>миксины</a>. Миксины - настоящая альтернатива правильному использованию наследования. С ними пропадает любая необходимость использовать наследование, включая абстрактные классы. В PHP концепция миксинов нашла отражение в виде конструкции<em>Trait</em>. Трейтам посвящён следующий урок.</p>
23
<p>Решение этой проблемы известно довольно давно и называется<a>миксины</a>. Миксины - настоящая альтернатива правильному использованию наследования. С ними пропадает любая необходимость использовать наследование, включая абстрактные классы. В PHP концепция миксинов нашла отражение в виде конструкции<em>Trait</em>. Трейтам посвящён следующий урок.</p>