Наследование программирование

Наследование является одной из главных особенностей объектно-ориентированного программи­рования. В С++ наследование поддерживается за счет того, что одному классу разрешается при своем объявлении включать в себя другой класс. Наследование позволяет построить иерархию классов от более общего к более частным. Этот процесс включает в себя определение базового класса, определяющего общие качества всех объектов, которые будут выведены затем из базового класса. Базовый класс представляет собой наиболее общее описание. Выведенные из базового класса классы обычно называют производными классами. Производные классы включают в себя все черты базового класса и, кроме того, добавляют новые качества, характерные именно для данного про­изводного класса. Чтобы продемонстрировать, как работает этот процесс, в следующем примере созданы классы, классифицирующие различные типы средств передвижения.
В качестве начального рассмотрим класс, названный road_vehicle (дорожное средство передви­жения), который служит очень широким определением средств передвижения по дорогам. Он хранит информацию о числе колес движущегося средства и о числе пассажиров, которые он может вмещать:

class road_vehicle <
int wheels;
int passengers;
public:
void set_wheels(int num);
int get_wheels();
void set_pass(int num);
int get_pass();
>;

Теперь можно использовать это определение дорожного средства передвижения для того, что­бы определить конкретные типы. Например, в следующем фрагменте кода определен класс truck (грузовик), используя класс road_vehicle:

class truck: public road_vehicle <
int cargo;
public:
void set_cargo(int size);
int get_cargo();
void show();
>;

Обратим внимание, каким образом наследуется класс road_vehicle. Общая форма записи насле­дования имеет следующий вид

class имя_нового_класса: доступ наследуемый_класс <
//тело нового класса
>

Здесь использование доступ факультативно, но если оно используется, то должно принимать значение public или private. Пока же все наследуемые классы будут использовать спецификатор public. Он означает, что все члены класса-предшественника, имеющие спецификатор доступа public, сохраняют тот же спецификатор досту­па во всех производных классах. Поэтому в данном примере члены класса truck имеют доступ к функциям-членам класса road_vehicle так, как если бы эти функции-члены были объявлены внут­ри класса truck. Однако функции-члены класса truck не имеют доступа к частным членам класса road_vehicle.

Следующая программа иллюстрирует наследование с помощью создания двух подклассов клас­са road_vehicle: truck и automobile:

#include
class road_vehicle <
int wheels;
int passengers;
public:
void set_wheels(int num);
int get_wheels ();
void set_pass(int num);
int get_pass ();
>;
class truck: public road_vehicle <
int cargo;
public:
void set_cargo(int size);
int get_cargo();
void show ();
>;
enum type ;
class automobile: public road_vehicle <
enum type car_type;
public:
void set_type (enum type t);
enum type get_type();
void show();
>;
void road_vehicle::set_wheels(int num)
<
wheels = num;
>
int road_vehicle::get_wheels()
<
return wheels;
>
void road_vehicle::set_pass(int num)
<
passengers = num;
>
int road_vehicle::get_pass()
<
return passengers;
>
void truck::set_cargo(int num)
<
cargo = num;
>
int truck::get_cargo ()
<
return cargo;
>
void truck::show ()
<
cout

Наследование — это механизм создания нового класса на основе уже существующего. При этом к существующему классу могут быть добавлены новые элементы (данные и функции), либо существующие функции могут быть изменены. Основное назначение механизма наследования — повторное использование кодов, так как большинство используемых типов данных являются вариантами друг друга, и писать для каждого свой класс нецелесообразно.
Объекты разных классов и сами классы могут находиться в отношении наследования, при котором формируется иерархия объектов, соответствующая заранее предусмотренной иерархии классов.

Иерархия классов позволяет определять новые классы на основе уже имеющихся. Имеющиеся классы обычно называют базовыми (иногда порождающими), а новые классы, формируемые на основе базовых, – производными (порожденными, классами-потомками или наследниками).

Производные классы «получают наследство» – данные и методы своих базовых классов, и могут пополняться собственными компонентами (данными и собственными методами). Наследуемые компоненты не перемещаются в производный класс, а остаются в базовых классах. Сообщение, обработку которого не могут выполнить методы производного класса, автоматически передается в базовый класс. Если для обработки сообщения нужны данные, отсутствующие в производном классе, то их пытаются отыскать автоматически в базовом классе.

При наследовании некоторые имена методов (функций-членов) и полей (данных-членов) базового класса могут быть по-новому определены в производном классе. В этом случае соответствующие компоненты базового класса становятся недоступными из производного класса. Для доступа из производного класса к компонентам базового класса, имена которых повторно определены в производном, используется операция разрешения контекста ::

Для порождения нового класса на основе существующего используется следующая общая форма

При объявлении порождаемого класса МодификаторДоступа может принимать значения public , private , protected либо отсутствовать, по умолчанию используется значение private . В любом случае порожденный класс наследует все члены базового класса, но доступ имеет не ко всем. Ему доступны общие ( public ) члены базового класса и недоступны частные ( private ).

Для того, чтобы порожденный класс имел доступ к некоторым скрытым членам базового класса, в базовом классе их необходимо объявить со спецификацией доступа защищенные ( protected ).

Члены класса с доступом protected видимы в пределах класса и в любом классе, порожденном из этого класса.

Общее наследование

При общем наследовании порожденный класс имеет доступ к наследуемым членам базового класса с видимостью public и protected . Члены базового класса с видимостью private – недоступны.

Наследование (программирование)

  • Найти и оформить в виде сносок ссылки на авторитетные источники, подтверждающие написанное.

Насле́дование — механизм объектно-ориентированного программирования (наряду с инкапсуляцией, полиморфизмом и абстракцией), позволяющий описать новый класс на основе уже существующего (родительского), при этом свойства и функциональность родительского класса заимствуются новым классом.

Другими словами, класс-наследник реализует спецификацию уже существующего класса (базовый класс). Это позволяет обращаться с объектами класса-наследника точно так же, как с объектами базового класса.

Типы наследования

Простое наследование

Класс, от которого произошло наследование, называется базовым или родительским (англ. base class ). Классы, которые произошли от базового, называются потомками, наследниками или производными классами (англ. derived class ).

В некоторых языках используются абстрактные классы. Абстрактный класс — это класс, содержащий хотя бы один абстрактный метод, он описан в программе, имеет поля, методы и не может использоваться для непосредственного создания объекта. То есть от абстрактного класса можно только наследовать. Объекты создаются только на основе производных классов, наследованных от абстрактного. Например, абстрактным классом может быть базовый класс «сотрудник вуза», от которого наследуются классы «аспирант», «профессор» и т. д. Так как производные классы имеют общие поля и функции (например, поле «год рождения»), то эти члены класса могут быть описаны в базовом классе. В программе создаются объекты на основе классов «аспирант», «профессор», но нет смысла создавать объект на основе класса «сотрудник вуза».

Множественное наследование

При множественном наследовании у класса может быть более одного предка. В этом случае класс наследует методы всех предков. Достоинства такого подхода в большей гибкости. Множественное наследование реализовано в C++. Из других языков, предоставляющих эту возможность, можно отметить Python и Эйфель. Множественное наследование поддерживается в языке UML.

Множественное наследование — потенциальный источник ошибок, которые могут возникнуть из-за наличия одинаковых имен методов в предках. В языках, которые позиционируются как наследники C++ (Java, C# и др.), от множественного наследования было решено отказаться в пользу интерфейсов. Практически всегда можно обойтись без использования данного механизма. Однако, если такая необходимость все-таки возникла, то, для разрешения конфликтов использования наследованных методов с одинаковыми именами, возможно, например, применить операцию расширения видимости — «::» — для вызова конкретного метода конкретного родителя.

Читайте так же:  Санитарно-гигиенические требования к организации рабочего места кондитера

Попытка решения проблемы наличия одинаковых имен методов в предках была предпринята в языке Эйфель, в котором при описании нового класса необходимо явно указывать импортируемые члены каждого из наследуемых классов и их именование в дочернем классе.

Большинство современных объектно-ориентированных языков программирования (C#, Java, Delphi и др.) поддерживают возможность одновременно наследоваться от класса-предка и реализовать методы нескольких интерфейсов одним и тем же классом. Этот механизм позволяет во многом заменить множественное наследование — методы интерфейсов необходимо переопределять явно, что исключает ошибки при наследовании функциональности одинаковых методов различных классов-предков.

Наследование (программирование)

  • Найти и оформить в виде сносок ссылки на авторитетные источники, подтверждающие написанное.

Насле́дование — механизм объектно-ориентированного программирования (наряду с инкапсуляцией, полиморфизмом и абстракцией), позволяющий описать новый класс на основе уже существующего (родительского), при этом свойства и функциональность родительского класса заимствуются новым классом.

Другими словами, класс-наследник реализует спецификацию уже существующего класса (базовый класс). Это позволяет обращаться с объектами класса-наследника точно так же, как с объектами базового класса.

Типы наследования

Простое наследование

Класс, от которого произошло наследование, называется базовым или родительским (англ. base class ). Классы, которые произошли от базового, называются потомками, наследниками или производными классами (англ. derived class ).

В некоторых языках используются абстрактные классы. Абстрактный класс — это класс, содержащий хотя бы один абстрактный метод, он описан в программе, имеет поля, методы и не может использоваться для непосредственного создания объекта. То есть от абстрактного класса можно только наследовать. Объекты создаются только на основе производных классов, наследованных от абстрактного. Например, абстрактным классом может быть базовый класс «сотрудник вуза», от которого наследуются классы «аспирант», «профессор» и т. д. Так как производные классы имеют общие поля и функции (например, поле «год рождения»), то эти члены класса могут быть описаны в базовом классе. В программе создаются объекты на основе классов «аспирант», «профессор», но нет смысла создавать объект на основе класса «сотрудник вуза».

Множественное наследование

При множественном наследовании у класса может быть более одного предка. В этом случае класс наследует методы всех предков. Достоинства такого подхода в большей гибкости. Множественное наследование реализовано в C++. Из других языков, предоставляющих эту возможность, можно отметить Python и Эйфель. Множественное наследование поддерживается в языке UML.

Множественное наследование — потенциальный источник ошибок, которые могут возникнуть из-за наличия одинаковых имен методов в предках. В языках, которые позиционируются как наследники C++ (Java, C# и др.), от множественного наследования было решено отказаться в пользу интерфейсов. Практически всегда можно обойтись без использования данного механизма. Однако, если такая необходимость все-таки возникла, то, для разрешения конфликтов использования наследованных методов с одинаковыми именами, возможно, например, применить операцию расширения видимости — «::» — для вызова конкретного метода конкретного родителя.

Попытка решения проблемы наличия одинаковых имен методов в предках была предпринята в языке Эйфель, в котором при описании нового класса необходимо явно указывать импортируемые члены каждого из наследуемых классов и их именование в дочернем классе.

Большинство современных объектно-ориентированных языков программирования (C#, Java, Delphi и др.) поддерживают возможность одновременно наследоваться от класса-предка и реализовать методы нескольких интерфейсов одним и тем же классом. Этот механизм позволяет во многом заменить множественное наследование — методы интерфейсов необходимо переопределять явно, что исключает ошибки при наследовании функциональности одинаковых методов различных классов-предков.

Наследование (программирование)

  • Найти и оформить в виде сносок ссылки на авторитетные источники, подтверждающие написанное.

Насле́дование — механизм объектно-ориентированного программирования (наряду с инкапсуляцией, полиморфизмом и абстракцией), позволяющий описать новый класс на основе уже существующего (родительского), при этом свойства и функциональность родительского класса заимствуются новым классом.

Другими словами, класс-наследник реализует спецификацию уже существующего класса (базовый класс). Это позволяет обращаться с объектами класса-наследника точно так же, как с объектами базового класса.

Типы наследования

Простое наследование

Класс, от которого произошло наследование, называется базовым или родительским (англ. base class ). Классы, которые произошли от базового, называются потомками, наследниками или производными классами (англ. derived class ).

В некоторых языках используются абстрактные классы. Абстрактный класс — это класс, содержащий хотя бы один абстрактный метод, он описан в программе, имеет поля, методы и не может использоваться для непосредственного создания объекта. То есть от абстрактного класса можно только наследовать. Объекты создаются только на основе производных классов, наследованных от абстрактного. Например, абстрактным классом может быть базовый класс «сотрудник вуза», от которого наследуются классы «аспирант», «профессор» и т. д. Так как производные классы имеют общие поля и функции (например, поле «год рождения»), то эти члены класса могут быть описаны в базовом классе. В программе создаются объекты на основе классов «аспирант», «профессор», но нет смысла создавать объект на основе класса «сотрудник вуза».

Множественное наследование

При множественном наследовании у класса может быть более одного предка. В этом случае класс наследует методы всех предков. Достоинства такого подхода в большей гибкости. Множественное наследование реализовано в C++. Из других языков, предоставляющих эту возможность, можно отметить Python и Эйфель. Множественное наследование поддерживается в языке UML.

Множественное наследование — потенциальный источник ошибок, которые могут возникнуть из-за наличия одинаковых имен методов в предках. В языках, которые позиционируются как наследники C++ (Java, C# и др.), от множественного наследования было решено отказаться в пользу интерфейсов. Практически всегда можно обойтись без использования данного механизма. Однако, если такая необходимость все-таки возникла, то, для разрешения конфликтов использования наследованных методов с одинаковыми именами, возможно, например, применить операцию расширения видимости — «::» — для вызова конкретного метода конкретного родителя.

Попытка решения проблемы наличия одинаковых имен методов в предках была предпринята в языке Эйфель, в котором при описании нового класса необходимо явно указывать импортируемые члены каждого из наследуемых классов и их именование в дочернем классе.

Большинство современных объектно-ориентированных языков программирования (C#, Java, Delphi и др.) поддерживают возможность одновременно наследоваться от класса-предка и реализовать методы нескольких интерфейсов одним и тем же классом. Этот механизм позволяет во многом заменить множественное наследование — методы интерфейсов необходимо переопределять явно, что исключает ошибки при наследовании функциональности одинаковых методов различных классов-предков.

Наследование программирование

… не было ни композиции, ни наследования, только код.

И был код неповоротливым, повторяющимся, нераздельным, несчастным, избыточным и измученным.

Основным инструментом для повторного использования кода была копипаста. Процедуры и функции были редкостью, подозрительными новомодными штучками. Вызов процедур был дорогим удовольствием. Части кода, отделенные от основной логики, вызывали недоумение!

Мрачные были времена.

Но вот лучик ООП воссиял над миром… Правда, несколько десятилетий 1 никто этого не замечал. Покуда не появился графический интерфейс 2 , которому, как выяснилось, очень-очень не хватало ООП. Когда нажимаешь на кнопку в окне, что может быть проще, чем отправить кнопке (или ее представителю) сообщение «Нажатие» 3 и получить результат?

И вот тут ООП взлетел. Было написано множество 4 книг, расплодились бесчисленные 5 статьи. Так что сегодня-то каждый может в объектно-ориентированное программирование, так?

Читайте так же:  Купить комнату за материнский капитал в костроме

Увы, код (и интернет) говорит, что не так

Самые жаркие споры и наибольшее непонимание, похоже, вызывает выбор между композицией и наследованием, зачастую выраженный мантрой «предпочитайте композицию наследованию». Вот об этом и поговорим.

Когда мантры вредят

В житейском плане «предпочитать композицию наследованию» в целом нормально, хоть я и не любитель мантр. Несмотря на то, что они зачастую и несут зерно истины, слишком легко поддаться соблазну и бездумно следовать лозунгу, не понимая, что за ним скрывается. А это всегда выходит боком.

Желтушные статьи с заголовками вроде «Наследование — зло» 6 тоже не по мне, особенно если автор пытается обосновать свои набросы, сначала неправильно применяя наследование, а потом делая вывод, что оно во всем виновато. Ну типа «молотки — отстой, потому что ими нельзя завинтить шуруп.»

Далее в статье я буду понимать под ООП «классический» объектный язык, который поддерживает классы со свойствами, методами и простое (одиночное) наследование. Никаких вам интерфейсов, примесей, аспектов, множественного наследования, делегатов, замыканий, лямбд, — ничего, кроме самых простых вещей:

  • Класс: именованная сущность из предметной области, возможно, имеющая предка (суперкласс), определенная как набор полей и методов.
  • Поле: именованное свойство с определенным типом, которое может, в частности, ссылаться на другой объект (см. композиция).
  • Метод: именованная функция или процедура, с параметрами или без них, реализующая какое-то поведение класса.
  • Наследование: класс может унаследовать — использовать по умолчанию — поля и методы своего предка. Наследование транзитивно: класс может наследоваться от другого класса, который наследуется от третьего, и так далее вплоть до базового класса (обычно — Object ), возможно, неявного. Наследник может переопределить какие-то методы и поля чтобы изменить поведение по умолчанию.
  • Композиция: если поле у нас имеет тип Класс, оно может содержать ссылку на другой объект этого класса, создавая таким образом связь между двумя объектами. Не влезая в дебри различий между простой ассоциацией, агрегированием и композицией, давайте «на пальцах» определим: композиция — это когда один объект предоставляет другому свою функциональность частично или полностью.
  • Инкапсуляция: мы обращаемся с объектами как с единой сущностью, а не как с набором отдельных полей и методов, тем самым скрываем и защищаем реализацию класса. Если клиентский код не знает ничего, кроме публичного интерфейса, он не может зависеть от деталей реализации.

Наследование фундаментально

Наследование — это фундаментальное понятие ООП. В языке программирования могут быть объекты и сообщения, но без наследования он не будет объектно-ориентированным (только основанным на объектах, но все еще полиморфным).

… как и композиция

Композиция это тоже фундаментальное свойство, причем любого языка. Даже если язык не поддерживает композицию (что редкость в наши дни), люди все равно будут мыслить категориями частей и компонентов. Без композиции было бы невозможно решить сложные задачи по частям.

(Инкапсуляция тоже вещь фундаментальная, но сейчас речь не о ней)

Так от чего весь сыр-бор?

Ну хорошо, и композиция, и наследование фундаментальны, в чем дело-то?

А дело в том, что можно подумать, что одно всегда может заменить другое, или что первое лучше или хуже второго. Разработка ПО — это всегда выбор разумного баланса, компромисс.

С композицией все более-менее просто, мы с ней постоянно сталкиваемся в жизни: у стула есть ножки, стена состоит из кирпичей и цемента и тому подобное. А вот наследование, несмотря на свое простое определение, может все усложнить и запутать, если хорошенько не поразмыслить над тем, как его применять. Наследование это весьма абстрактная штука, о нем можно рассуждать, но так просто его не потрогаешь. Мы, конечно, можем сымитировать наследование, используя композицию, но это, как правило, слишком много возни. Для чего нужна композиция — очевидно: из частей собрать целое. А вот с наследованием сложнее, потому что оно сразу о двух вещах: о смысле и о механике.

Наследование смысловое

Как в биологии классификация таксонов организует их в иерархии, так наследование отражает иерархию понятий из предметной области. Упорядочивает их от общего к частному, собирает родственные идеи в ветви иерархического древа. Смысл (семантика) класса по большей части выражен в его интерфейсе — наборе сообщений, которые класс способен понять, но также определяется и теми сообщениями, которыми класс отвечает. Унаследовался от предка — будь добр не только понять все сообщения, которые мог понять предок, но также и уметь ответить как он (сохранить поведение предка — прим. пер.) И поэтому наследование связывает наследника с предком гораздо сильнее, чем если бы мы взяли просто экземпляр предка как компонент. Обратите внимание, даже если класс делает что-то совсем простое, почти не имеет логики, его имя несет существенную смысловую нагрузку, разработчик делает из него важные выводы о предметной области.

Наследование механическое

Говоря о наследовании в механическом плане, мы имеем в виду, что наследование берет данные (поля) и поведение (методы) базового класса и позволяет использовать их повторно или же дополнить в наследниках. С точки зрения механики, если потомок унаследует реализацию (код) предка, то неизбежно получит и его интерфейс.

Я уверен, что в недопонимании виновата именно эта двойственная природа наследования 7 в большинстве ОО-языков. Многие считают, что наследование — это чтобы повторно использовать код, хотя оно не только для этого. Если придавать повторному использованию чрезмерное значение — жди беды в архитектуре. Вот пара примеров.

Как не надо наследовать. Пример 1

Казалось бы, класс Stack , все хорошо. Но посмотрите внимательно на его интерфейс. Что должно быть в классе с именем Stack? Методы push() и pop() , что же еще. А у нас? У нас есть get() , set() , add() , remove() , clear() и еще куча барахла, доставшегося от ArrayList , которое стеку ну вообще не нужно.

Можно было бы переопределить все нежелательные методы, а некоторые (например, clear() ) даже и адаптировать под наши нужды, но не многовато ли работы из-за одной ошибки в дизайне? На самом деле трех: одной смысловой, одной механической и одной комбинированной:

  1. Утверждение «Stack это ArrayList» ложно. Stack не является подтипом ArrayList . Задача стека — обеспечить выполнение правила LIFO (последним пришел, первым ушел), которое легко удовлетворяется интерфейсом push/pop, но никак не соблюдается интерфейсом ArrayList .
  2. Механически наследование от ArrayList нарушает инкапсуляцию. Клиентскому коду не должно быть известно, что мы решили использовать ArrayList для хранения элементов стека.
  3. Ну и наконец, реализуя стек через ArrayList мы смешиваем две разные предметные области: ArrayList — это коллекция с произвольным доступом, а стек — это понятие из мира очередей, со строго ограниченным (а не произвольным) 8 доступом.

Последний пункт — незначительная на первый взгляд, но важная вещь. Посмотрим на нее пристальнее.

Как не надо наследовать. Пример 2

Частая ошибка при наследовании — это создать модель из предметной области, унаследовав ее от готовой реализации. Вот, скажем, нам надо выделить некоторых наших клиентов (класс Customer ) в определенное подмножество. Легко! Наследуемся от ArrayList , называем это CustomerGroup и понеслась.

Не тут-то было. Поступив так мы опять спутаем две предметные области. Старайтесь избегать этого:

  1. ArrayList это уже наследник списка, утилиты типа «коллекция», готовой реализации.
  2. CustomerGroup это совсем другая штука — класс из предметной области (домена).
  3. Классы из предметной области должны использовать реализации, а не наследовать их.
Читайте так же:  Агентский договор на оказание услуг бухучет

Слой предметной области не должен знать, как у нас там все внутри сделано. Рассуждая о том, что делает наша программа, мы оперируем понятиями из предметной области, и мы не хотим отвлекаться на нюансы внутреннего устройства. Если видеть в наследовании только инструмент повторного использования кода, мы раз за разом будем попадаться в эту ловушку.

Дело не в одиночном наследовании

Одиночное наследование пока остается самой популярной моделью ООП. Оно неизбежно влечет наследование реализации, которое приводит к сильному зацеплению (coupling — прим. пер.) между классами. Может показаться, что беда в том, что ветка наследования у нас только одна на обе потребности: и смысловую и механическую. Если использовали для одного, то для другого уже нельзя. А раз так, может быть множественное наследование все исправит?

Нет. Отношение наследования не должно пересекать границы между предметными областями: инструментальной (структуры данных, алгоритмы, сети) и прикладной (бизнес-логика). Если CustomerGroup будет наследовать ArrayList и одновременно, скажем, DemographicSegment, то две предметные области переплетутся между собой, а «видовая принадлежность» объектов станет неочевидна.

Предпочтительно (по крайней мере, с моей точки зрения) делать так. Наследуемся от имеющихся в языке инструментальных классов по минимуму, ровно настолько, чтобы реализовать «механическую» часть вашей логики. Потом соединяем получившиеся части композицией, но не наследованием. Иными словами:

От инструментов можно наследовать только другие инструменты.

Это очень частая ошибка новичков. Что не удивительно, ведь так просто взять и унаследовать. Редко где встретишь обсуждения, почему именно это неправильно. Еще раз: бизнес-сущности должны пользоваться инструментами, а не быть ими. Мухи (инструменты) — отдельно, котлеты (бизнес-модели) — отдельно.

Так когда же нужно наследование?

Наследуемся как надо

Чаще всего — и при этом с наибольшей отдачей — наследование применяют для описания объектов, незначительно отличающихся друг от друга (в оригинале используется термин «differential programming» — прим. пер.) Например, нам нужна особенная кнопка с небольшими дополнениями. Нормально, наследуемся от существующего класса Кнопка. Потому что наш новый класс, это все еще кнопка, а мы полностью наследуем API класса Кнопка, его поведение и реализацию. Новая функциональность только добавляется к существующему. А вот если в наследнике часть функциональности убирается, это повод задуматься, а нужно ли наследование.

Наследование полезнее всего для группировки сходных сущностей и понятий, определения семейств классов, и вообще для организации терминов и понятий, описывающих предметную область. Зачастую, когда значительная часть предметной логики уже реализована, исходно выбранные иерархии наследования перестают работать. Если всё к тому идет, не бойтесь разобрать и заново сложить эти иерархии 9 так, чтобы они лучше соответствовали и работали друг с другом.

Композиция или наследование: что выбрать?

В ситуации, когда вроде бы подходит и то и другое, взгляните на дизайн в двух плоскостях:

  1. Структура и механическое исполнение бизнес-объектов.
  2. Что они обозначают по смыслу и как взаимодействуют.

Пока наследование остается внутри одной плоскости, все нормально. Но если иерархия проходит через две плоскости сразу, это плохой симптом.

Например, у вас есть один объект внутри другого. Внутренний объект реализует значительную часть поведения внешнего. У внешнего объекта куча прокси-методов, которые тупо пробрасывают параметры во внутренний объект и возвращают от него результат. В этом случае посмотрите, а не стоит ли унаследоваться от внутреннего объекта, хотя бы частично.

Разумеется, никакие инструкции не заменят голову на плечах. Когда строишь объектную модель, вообще полезно думать. Но если вам хочется конкретных правил, то пожалуйста.

  1. Оба класса из одной предметной области
  2. Наследник является корректным подтипом (в терминах LSP — прим. пер.) предка
  3. Код предка необходим либо хорошо подходит для наследника
  4. Наследник в основном добавляет логику

Иногда все эти условия выполняются одновременно:

  • в случае моделирования высокоуровневой логики из предметной области
  • при разработке библиотек и расширений для них
  • при дифференциальном программировании (автор снова использует термин «differential programming», очевидно, понимая под ним нечто, отличное от DDP — прим. пер.)

Если это не ваш случай, то и наследование вам, скорее всего, будет нужно не часто. Но не потому, что надо «предпочитать» композицию наследованию, и не потому что она «лучше». Выбирайте то, что подходит наилучшим образом для конкретно вашей задачи.

Надеюсь, эти правила помогут вам понять разницу между двумя подходами.

Отдельная благодарность сотрудникам ThoughtWorks за их ценный вклад и замечания: Питу Хогсону, Тиму Брауну, Скотту Робинсону, Мартину Фаулеру, Минди Ор, Шону Ньюхэму, Сэму Гибсону и Махендре Кария.

Первый официальный ОО-язык, SIMULA 67, появился в 1967 году.

Системные и прикладные программисты приняли на вооружение C++ в середине 1980-х, но перед тем, как ООП стал общепринятым, прошел еще десяток лет.

Я намеренно упрощаю, не говорю про паб/саб, делегатов и тому подобное, чтобы не раздувать статью.

На момент написание этого текста Амазон предлагает 24777 книг по ООП.

Поиск в гугле по фразе «объектно-ориентированное программирование» дает 8 млн результатов.

Поиск в гугле выдает 37600 результатов по запросу «наследование это зло».

Смысл (интерфейс) и механику (исполнение) можно разделить за счет усложнения языка. См. пример из спецификации языка D.

С грустью замечу, что в Java Stack унаследован от Vector .

Проектирование для повторного использования через наследования выходит за рамки темы статьи. Просто имейте в виду, что ваш дизайн должен удовлетворить потребности и тех, кто пользуется базовым классом, и тех, кому нужен наследник.

Переводчик выражает благодарность ООП-чату в Telegram, без которого этот текст не смог бы появиться.

Другие статьи:

  • Нотариус сша Публичный нотариус США (Notary Public) заверяет только подпись, но не полномочия О публичных нотариусах (notary public) США, Канады и др. стран англосаксонской правовой системы - они только подпись заверяют или копии. В отличии от Российской Федерации, США (кроме […]
  • Залог комфорта Межкомнатные двери – залог удобства и комфорта Межкомнатные двери появились достаточно давно. Изначально, они появились в древнем Китае, во времена, когда книги были невероятной ценностью и их охраняли больше, чем императора. Собственно, именно в императорской […]
  • Заместитель прокурора домодедово Уволен зампрокурора Московской области Неожиданные новости поступили из прокуратуры Московской области. В ряде СМИ появилась информация о том, что заместитель прокурора Московской области Станислав Буянский несанкционированно проник на территорию […]
  • Ломбард залог петрозаводск Спасибо за сообщение. ломбард ООО Залог занимается: кредитами под залог, займами под залог, приемом ноутбуков, экспресс займами, продажей в ломбарде, меховыми изделиями, приемом серег, кредитами под залог автомобилей. Также мы занимаемся: приемом телевизоров, […]
  • Полюс осаго казань Агент "ОСАГО Казань": Оформление ОСАГО с бесплатной доставкой в пределах г.Казани. т. +7 843 267-59-30 с 09.00 до 19.00 без выходных! Больше не надо ехать к страховщику и тратить время в очередях: Присылайте данные для ОСАГО - и мы привезем готовый полис к вам […]
  • Штраф за административное правонарушение беларусь Кодекс Республики Беларусь об Административных ПравонарушенияхСтатья 6.5. Штраф 1. Штраф является денежным взысканием, размер которого определяется в белорусских рублях исходя из базовой величины, установленной законодательством на день вынесения постановления о […]
  • Заявление на банковский перевод составляет Сущность банковских переводов. Банковский перевод – это безналичная форма оплаты за услуги. Заключается в осуществлении платежа одной стороной (Плательщиком) другой стороне (Получателю). Перевод осуществляется посредством факсимильной, телеграфной или электронной […]