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
<h2>Ограничения применения наследования</h2>
3
<h2>Ограничения применения наследования</h2>
4
<p>Ключевая проблема с иерархиями в том, что наш мир не иерархичен. Любая классификация всегда опирается на конкретный признак, который интересует нас в конкретный момент времени. И эта же классификация становится бесполезной, если берется другой признак.</p>
4
<p>Ключевая проблема с иерархиями в том, что наш мир не иерархичен. Любая классификация всегда опирается на конкретный признак, который интересует нас в конкретный момент времени. И эта же классификация становится бесполезной, если берется другой признак.</p>
5
<p>Это хорошо видно в интернет-магазинах, у которых навороченные фильтры для выбора товара: группировка по производителю, по применимости, по безопасности для детей и так далее. В каждой конкретной ситуации будет своя структура.</p>
5
<p>Это хорошо видно в интернет-магазинах, у которых навороченные фильтры для выбора товара: группировка по производителю, по применимости, по безопасности для детей и так далее. В каждой конкретной ситуации будет своя структура.</p>
6
<p>Возьмем понятие <em>User</em>. Статьи по наследованию часто любят показывать иерархии пользователей, создавая у разработчиков уверенность, что мир так и устроен.</p>
6
<p>Возьмем понятие <em>User</em>. Статьи по наследованию часто любят показывать иерархии пользователей, создавая у разработчиков уверенность, что мир так и устроен.</p>
7
<p>Попробуем представить, по каким признакам можно построить иерархию пользователей:</p>
7
<p>Попробуем представить, по каким признакам можно построить иерархию пользователей:</p>
8
<ul><li>По полу (MaleUser, FemaleUser)</li>
8
<ul><li>По полу (MaleUser, FemaleUser)</li>
9
<li>На основе аутентификации (User, Guest)</li>
9
<li>На основе аутентификации (User, Guest)</li>
10
<li>По роли (Admin, Member)</li>
10
<li>По роли (Admin, Member)</li>
11
<li>По типу должности (Marketer, SalesManager, Programmer, Tester, Player)</li>
11
<li>По типу должности (Marketer, SalesManager, Programmer, Tester, Player)</li>
12
<li>По принадлежности к какой-либо группе (UserFromRussia, UserWhoLikesSpartak)</li>
12
<li>По принадлежности к какой-либо группе (UserFromRussia, UserWhoLikesSpartak)</li>
13
<li>По источнику (UserFromFacebook, UserFromGithub)</li>
13
<li>По источнику (UserFromFacebook, UserFromGithub)</li>
14
<li>По типу хранилища (SQLUser, LocalStorageUser)</li>
14
<li>По типу хранилища (SQLUser, LocalStorageUser)</li>
15
</ul><p>Всё это может и будет встречаться в рамках даже одной программы. В зависимости от того, какую задачу мы решаем, может понадобиться разное представление.</p>
15
</ul><p>Всё это может и будет встречаться в рамках даже одной программы. В зависимости от того, какую задачу мы решаем, может понадобиться разное представление.</p>
16
<p>Наследование ограничивает свободу действий. Оно приковывает нас к определенной структуре, которую невозможно изменить. Единственным решением в данном контексте является использование большего числа наследований. В итоге у нас будет комбинация всех возможных поведений, которые встретятся в программе, а это иерархии с десятками и сотнями классов. При этом это должно согласовываться с интерфейсами, которые тоже могут расширять друг друга.</p>
16
<p>Наследование ограничивает свободу действий. Оно приковывает нас к определенной структуре, которую невозможно изменить. Единственным решением в данном контексте является использование большего числа наследований. В итоге у нас будет комбинация всех возможных поведений, которые встретятся в программе, а это иерархии с десятками и сотнями классов. При этом это должно согласовываться с интерфейсами, которые тоже могут расширять друг друга.</p>
17
<p>Можно использовать множественное наследование, но в некоторых языках оно делает все еще сложнее. Поэтому от него отказались.</p>
17
<p>Можно использовать множественное наследование, но в некоторых языках оно делает все еще сложнее. Поэтому от него отказались.</p>
18
<p>В итоге у разработчиков сформировалась общая позиция по отношению к наследованию: композиция вместо наследования. Если попробовать загуглить эту фразу, то поисковик покажет невероятное количество статей по этой теме.</p>
18
<p>В итоге у разработчиков сформировалась общая позиция по отношению к наследованию: композиция вместо наследования. Если попробовать загуглить эту фразу, то поисковик покажет невероятное количество статей по этой теме.</p>
19
<p>Этот подход мы уже изучали в курсе "Python: Полиморфизм". Он сводится к более грамотному разделению зон ответственности в приложении и делегированию функциональности другим объектам, нужным в конкретных ситуациях.</p>
19
<p>Этот подход мы уже изучали в курсе "Python: Полиморфизм". Он сводится к более грамотному разделению зон ответственности в приложении и делегированию функциональности другим объектам, нужным в конкретных ситуациях.</p>
20
<p>С этого момента начинаются сложности. В большинстве статей, посвященных этому вопросу, приводятся либо ошибочные, либо слишком искусственные примеры, которые не дают особого понимания.</p>
20
<p>С этого момента начинаются сложности. В большинстве статей, посвященных этому вопросу, приводятся либо ошибочные, либо слишком искусственные примеры, которые не дают особого понимания.</p>
21
<p>Для начала отделим две разные причины использования наследования. Одна из них связана с прямым назначением наследования, другая вытекает из неверного понимания принципов организации кода.</p>
21
<p>Для начала отделим две разные причины использования наследования. Одна из них связана с прямым назначением наследования, другая вытекает из неверного понимания принципов организации кода.</p>
22
<h2>Использование не по назначению</h2>
22
<h2>Использование не по назначению</h2>
23
<p>Яркий пример использования наследования не по назначению - смешивание разных уровней абстракции. Выше был пример про пользователей, разделяемых по типу хранилища - <em>SQLUser</em>.</p>
23
<p>Яркий пример использования наследования не по назначению - смешивание разных уровней абстракции. Выше был пример про пользователей, разделяемых по типу хранилища - <em>SQLUser</em>.</p>
24
<p>Можно задаться вопросом, как пользователь с точки зрения нашей предметной области связан с техническими аспектами хранения этих пользователей? Никак не связан, такой код в принципе не должен существовать, и наследование для него не предназначено.</p>
24
<p>Можно задаться вопросом, как пользователь с точки зрения нашей предметной области связан с техническими аспектами хранения этих пользователей? Никак не связан, такой код в принципе не должен существовать, и наследование для него не предназначено.</p>
25
<p>Такой код появляется не от незнания ООП, а от непонимания общих принципов организации кода, построения абстракций. Эта тема не является специфичной для ООП, но ООП делает ее сложнее из-за большого числа новых сущностей, которое оно вводит.</p>
25
<p>Такой код появляется не от незнания ООП, а от непонимания общих принципов организации кода, построения абстракций. Эта тема не является специфичной для ООП, но ООП делает ее сложнее из-за большого числа новых сущностей, которое оно вводит.</p>
26
<p>Обязательно прочитайте <a>эту статью</a>, которая проливает свет на архитектуру.</p>
26
<p>Обязательно прочитайте <a>эту статью</a>, которая проливает свет на архитектуру.</p>
27
<p>Парадокс состоит в том, что фраза composition over inheritance (композиция вместо наследования) относится к такому использованию наследования. То есть проблема не в наследовании. Проблема в том, что оно оказалось удобным способом организации кода для тех, кто не очень хорошо знает, как его организовывать, что такое барьеры абстракции и слои приложения.</p>
27
<p>Парадокс состоит в том, что фраза composition over inheritance (композиция вместо наследования) относится к такому использованию наследования. То есть проблема не в наследовании. Проблема в том, что оно оказалось удобным способом организации кода для тех, кто не очень хорошо знает, как его организовывать, что такое барьеры абстракции и слои приложения.</p>
28
<h2>Использование по назначению</h2>
28
<h2>Использование по назначению</h2>
29
<p>Необходимость наследования классов возникает там, где классы связаны общим кодом. И это не отношение подтипов. В такой ситуации нужна альтернатива наследованию. И здесь появляются варианты.</p>
29
<p>Необходимость наследования классов возникает там, где классы связаны общим кодом. И это не отношение подтипов. В такой ситуации нужна альтернатива наследованию. И здесь появляются варианты.</p>
30
<p>В самом простом случае общий код - не публичный. Тогда хватит обычной функции, которую эти классы будут использовать внутри себя. А если общий код был публичным? Большинство руководств рекомендуют создать соответствующий интерфейс и реализовать его в каждом из классов. Основной недостаток такого подхода - дублирование кода в каждом классе. То есть мы пришли к тому, от чего пытались уйти.</p>
30
<p>В самом простом случае общий код - не публичный. Тогда хватит обычной функции, которую эти классы будут использовать внутри себя. А если общий код был публичным? Большинство руководств рекомендуют создать соответствующий интерфейс и реализовать его в каждом из классов. Основной недостаток такого подхода - дублирование кода в каждом классе. То есть мы пришли к тому, от чего пытались уйти.</p>
31
<p>Решение этой проблемы известно довольно давно и называется <a>миксинами</a>. Это альтернатива правильному использованию наследования. С ними пропадает любая необходимость использовать наследование, включая абстрактные классы.</p>
31
<p>Решение этой проблемы известно довольно давно и называется <a>миксинами</a>. Это альтернатива правильному использованию наследования. С ними пропадает любая необходимость использовать наследование, включая абстрактные классы.</p>
32
<h2>Выводы</h2>
32
<h2>Выводы</h2>
33
<p>Наследование является сложным и многогранным механизмом, и его использование требует тщательного понимания и внимания к деталям. Неправильное использование может привести к проблемам в коде и снижению его поддерживаемости. Однако, если знать альтернативы, такие как миксины и правильное использование композиции, разработчики могут избежать этих проблем и создать более гибкие и модульные системы.</p>
33
<p>Наследование является сложным и многогранным механизмом, и его использование требует тщательного понимания и внимания к деталям. Неправильное использование может привести к проблемам в коде и снижению его поддерживаемости. Однако, если знать альтернативы, такие как миксины и правильное использование композиции, разработчики могут избежать этих проблем и создать более гибкие и модульные системы.</p>