C++. Наследование. Общие понятия. Использование модификаторов private, protected, public
Наследование. Общие понятия. Использование модификаторов private , protected , public при наследовании
Поиск на других ресурсах:
1. Повторное использование кода. Наследование
Идея наследования в программировании взята из природы и начинается еще с 60-х годов 20 века. В языке C++ концепция классов существенно усиливается благодаря внедрению наследованию в классах. В природе наследование позволяет добавлять к родительским качествам новые навыки. В программировании наследование – это свойство класса получать программный код (навыки) другого (базового) класса, добавляя к нему свой собственный код, тем самым расширяя его возможности.
С помощью механизма наследования можно без ограничений изменять любой класс, разработанный собственно программистом или другими программистами. При этом не следует перестраивать структуру этого класса. В базовый класс добавляются новые возможности. Возможности базового класса в унаследованном классе могут быть расширены, изменены, сужены, уничтожены или оставлены без изменений.
Наследование свойственно только классам и их характеристикам, а не переменным или функциям.
Проекты на языке C++ базируются на использовании конкретных классов, решающих поставленные задачи. Благодаря использованию наследования классы строятся постепенно, от базовых простых классов до специализированных классов, постепенно детализирующих решение. В итоге классы образуют иерархию классов. В этих иерархиях классы верхних уровней (базовые классы) описывают некоторые общие для всех характеристики, которые на нижних уровнях детализируются в унаследованных классах. Завершенный программный проект есть работоспособной системой, состоящей из иерархически связанных между собой классов. Количество классов в некоторых проектах может составлять десятки или сотни.
В отличие от некоторых языков программирования, в языке C++ разрешено множественное наследование. При множественном наследовании один класс может быть унаследован из нескольких базовых классов, получая их свойства и поведение.
В контексте понятия наследования определяется понятие повторного использования кода, которое в программировании на C++ определено двумя аспектами:
- создание класса с целью получения экземпляров;
- создание класса для его использования в качестве базового, передающего свои характеристики унаследованным классам.
Порождение кода от базового класса является эффективным способом использования уже написанного кода для собственных нужд. Наследование является одной из частей повторного использования кода.
2. Синтаксис объявления классов, образующих иерархию. Базовый класс и производный (унаследованный) класс
Для реализации наследования требуется наличие как минимум двух классов. Если в программе класс с именем B должен быть унаследован от класса с именем A , то объявление классов выглядит следующим образом:
class A < // Элементы класса A // . >; class B : A < // Составляющие элементы класса B // . >;
В вышеприведенном объявлении класс B наследует часть характеристик или все характеристики класса A . Из класса B может быть унаследован другой (третий) класс, который получит часть или все характеристики классов A и B . Кроме того, из класса A могут быть унаследованы один или несколько классов, которые по отношению к классу B образуют параллельную ветвь иерархии. На рисунке 1 представлен один из возможных вариантов образования классами древовидной иерархии наследования.

Рисунок 1. Наследование. Дерево наследования, созданное классами
3. Передача характеристик наследуемому классу. Варианты реализации. Ограничение и расширение доступа. Ключевые слова private , protected , public
Важным при наследовании является вопрос: как передать характеристики базового класса в унаследованный класс? Здесь C++ дает широкий спектр возможных вариантов реализации.
Для указания того, какие элементы базового класса должны быть доступны в унаследованном классе, используется механизм инкапсуляции. Основой этого механизма является использование ключевых слов private , protected , public для предоставления доступа или запрета доступа к элементам базового класса.
3.1. Взаимодействие двух классов. Доступ из унаследованного класса к элементам базового класса
Элемент базового класса (переменная, функция) может быть объявлен с одним из трех модификаторов доступа:
- private (скрытый). В этом случае доступа к этому элементу из унаследованного класса нет;
- protected (защищенный) – позволяет использовать элемент базового класса в унаследованном классе;
- public (общедоступный) – в унаследованном классе действует так же, как protected .
На рисунке 2 показаны все 3 вида доступа для двух классов, образующих иерархию наследования. Демонстрируется доступ к переменной a базового класса A из функции Func() унаследованного класса B . Те же правила действуют не только для переменных, но и для функций базового класса A .

Рисунок 2. Наследование для двух классов A и B . К private -членам базового класса доступ из унаследованного класса запрещен. К protected — и public — членам базового класса доступ разрешен
3.2. Взаимодействие двух классов. Доступ из экземпляра (объекта) унаследованного класса к элементам базового класса с использованием модификаторов доступа private и protected
Если рассматривать доступ из экземпляра (объекта) унаследованного класса к элементам базового класса, то отличие от предыдущего случая (пункт 3.1) заключается в использовании ключевого слова protected и способе наследования класса. Класс может быть унаследован как private , protected или public . Если класс унаследован как private , то ключевое слово private указывать не обязательно (см. рисунок 2).
На рисунке 3 изображены возможные варианты доступа к элементам базового класса из экземпляра унаследованного класса. В любом варианте наследования базового класса доступ к элементам этого класса из экземпляра производного класса запрещен.

Рисунок 3. Модификаторы доступа private , protected запрещают доступ к элементам класса из любого экземпляра класса
3.3. Взаимодействие двух классов. Доступ к элементам базового класса из производного класса. Модификатор доступа public
Если в базовом классе A некий элемент объявляется с модификатором доступа public , то экземпляр унаследованного класса B :
- имеет доступ к элементам базового класса A , если производный класс B наследует класс A с модификатором доступа public ;
- не имеет доступа к элементам базового класса A , если производный класс B наследует класс A как private — или protected -.
На рисунке 4 наглядно показаны все возможные варианты такого доступа.

Рисунок 4. Доступ к public -элементам базового класса: 1) базовый класс унаследован как private -класс; 2) базовый класс унаследован как protected -класс; 3) базовый класс унаследован как public -класс.
4. Наследование 3-х и более классов. Доступ из унаследованных классов
Как было показано выше, базовый класс может быть унаследован путём указания перед его именем модификаторов private , protected , public . Отличие в использовании этих модификаторов проявляется в классе, являющемся производным от унаследованного класса. То есть, если дано три класса с именами A , B , C , последовательно наследующими друг друга, то влияние модификаторов будет именно на класс C .
4.1. Доступ к private -базовому классу из производных классов
На рисунке 5 изображен доступ к базовому классу A из производного класса C . Класс A в классе B наследуется как private -класс (скрытый). Это означает, что доступ к любому элементу класса A из производного класса C запрещен.

Рисунок 5. Запрещен доступ к элементам private -класса A из унаследованного класса C
4.2. Доступ к protected — и public — базовому классу из производных классов
Если базовый класс A унаследован с модификаторами protected или public , то элементы этого класса доступны в унаследованных классах B и C .

Рисунок 6. Модификаторы protected и public перед именем базового класса. Открытие доступа к protected — и public -элементам базового класса
Связанные темы
- Порядок вызова конструкторов при наследовании. Ограничения наследования. Свойства указателя на базовый класс
- Полиморфизм. Виртуальные функции. Общие понятия. Спецификаторы virtual , override . Примеры
- Абстрактный класс. Чисто виртуальная функция. Примеры
Private наследование
Для чего нужно наследование с описателем доступа private, если все поля базового класса станут недоступными?
Отслеживать
1,342 3 3 золотых знака 14 14 серебряных знаков 27 27 бронзовых знаков
задан 16 янв 2016 в 11:06
161 1 1 серебряный знак 3 3 бронзовых знака
Если вам дан исчерпывающий ответ, отметьте его как верный (галка напротив выбранного ответа).
17 янв 2016 в 8:37
3 ответа 3
Сортировка: Сброс на вариант по умолчанию
Именно для того, чтобы все члены базового класса не были доступны снаружи (были доступны только изнутри класса, или друзьям класса).
Наследование мало чем отличается от аггрегации, и
class A : private B < . >; аналогично
class A < private: B b; >; .
Одна в отличие от private: B b; , при наследовании функции B можно вызывать как члены своего класса, т.е. не b.f() , а просто f() .
Также можно унаследоваться от абстрактного класса, и переопределить его методы, не показывая все эти детали наружу, например:
struct Callback < virtual void done() = 0; >; void run(Callback* b); class Cls : private Callback < public: void some() < run(this); >private: void done() override < . >>;
Отслеживать
206k 28 28 золотых знаков 291 291 серебряный знак 526 526 бронзовых знаков
ответ дан 16 янв 2016 в 11:11
30.9k 13 13 золотых знаков 96 96 серебряных знаков 157 157 бронзовых знаков
А зачем такое вообще может пригодится? Ну кроме потому что можно?
16 янв 2016 в 11:12
@Flownee Чтоб приватные члены базового класса остались в производном так же скрытыми от остальных, чтоб не получились публичные члены класса (что всегда подозрительно)
16 янв 2016 в 11:18
в примере Callback не должен был зваться Base ?
16 янв 2016 в 11:22
@Flowneee, это наследование функционала без отношения «is», т.е. после такого наследования класс наследник не может быть преобразован к родителю.
16 янв 2016 в 12:08
@Flowneee, как и почти все в ООП для того, чтобы автору было приятно писать, а остальным (при исправлении багов) было сложнее быстро и точно понять, а почему же вся эта фигня не работает.
16 янв 2016 в 13:18
Приватное наследование — это по существу композиция, выраженная немного по-другому. При композиции у вас вложенный объект доступен по своему имени, но не виден снаружи. При приватном наследовании вложенный объект «влит» в *this .
Вообще, роль наследования — выражать отношение Is-A между классами. То есть, выражать наследование интерфейса, при этом наследование имплементации есть техническая деталь. А приватное наследование именно это и не может, при приватном наследовании наследуется именно имплементация, но не интерфейс.
Поэтому практически всегда вместо приватного наследования стоит предпочесть именно композицию: она выражает ту же идею, но явно.
Отслеживать
ответ дан 16 янв 2016 в 12:15
206k 28 28 золотых знаков 291 291 серебряный знак 526 526 бронзовых знаков
Читал ваш ответ, и в голову пришла мысль — возможно использование приватного наследования будет эффективнее по производительности чем явное использование агрегации (меньше потребления памяти) + меньше инструкций на вызов сокрытых методов
Наследование. Закрытое/частное (private) и защищенное (protected) наследование / FAQ C++
Закрытое наследование – это синтаксический вариант композиции (она же агрегация и/или связь типа «имеет»).
Например, связь « Car (автомобиль) имеет Engine (двигатель)» можно выразить с помощью простой композиции:
class Engine < public: Engine(int numCylinders); void start(); // запускает этот "двигатель" >; class Car < public: Car() : e_(8) < >// Инициализирует автомобиль (Car) с 8 цилиндрами void start() < e_.start(); >// Запускает автомобиль (Car), запуская его двигатель (Engine) private: Engine e_; // Автомобиль (Car) имеет двигатель (Engine) >;
Связь « Car имеет Engine » также можно выразить с помощью закрытого (частного) наследования:
class Car : private Engine < // Автомобиль (Car) имеет двигатель (Engine) public: Car() : Engine(8) < >// Инициализирует автомобиль (Car) с 8 цилиндрами using Engine::start; // Запускает автомобиль (Car), запуская его двигатель (Engine) >;
Между этими двумя вариантами есть несколько общих черт:
- в обоих случаях в каждом объекте Car содержится ровно один объект-член Engine ;
- ни в том, ни в другом случае пользователи (посторонние) не могут преобразовать Car* в Engine* ;
- в обоих случаях класс Car имеет метод start() , который вызывает метод start() содержащегося в нем объекта Engine .
Также есть несколько отличий:
- вариант простой композиции нужен, если вы хотите иметь в Car несколько объектов Engine ;
- вариант с частным наследованием может ввести ненужное множественное наследование;
- вариант с частным наследованием позволяет членам Car преобразовывать Car* в Engine* ;
- вариант с частным наследованием позволяет получить доступ к защищенным членам базового класса;
- вариант с частным наследованием позволяет Car переопределять виртуальные функции Engine ;
- вариант с частным наследованием немного упрощает (20 символов по сравнению с 28 символами) предоставление Car метода start() , который просто вызывает метод start() класса Engine .
Обратите внимание, что частное наследование обычно используется для получения доступа к защищенным членам базового класса, но обычно это краткосрочное решение (перевод: пластырь).
Что предпочесть: композицию или частное наследование?
Используйте композицию, когда можете, а частное наследование, когда вам нужно.
Обычно вы не хотите иметь доступ к внутренним компонентам слишком многих других классов, а частное наследование дает вам некоторые из этих дополнительных полномочий (и ответственностей). Но частное наследование – это не зло; просто его дороже поддерживать, так как оно увеличивает вероятность того, что кто-то изменит что-то, что нарушит ваш код.
Легальное, долгосрочное использование частного наследования – это когда вы хотите создать класс Fred , который использует код в классе Wilma , а код из класса Wilma должен вызывать функции-члены из вашего нового класса Fred . В этом случае Fred вызывает невиртуальные функции в Wilma , а Wilma вызывает в себе (обычно чисто) виртуальные функции, которые перегружены классом Fred . Сделать это с помощью композиции будет намного сложнее.
class Wilma < protected: void fredCallsWilma() < std::cout virtual void wilmaCallsFred() = 0; // чисто виртуальная функция >; class Fred : private Wilma < public: void barney() < std::cout protected: virtual void wilmaCallsFred() < std::cout >;
Должен ли я приводить указатель из производного класса с частным наследованием к его базовому классу?
Как правило, нет.
Об отношении к базовому классу из функции-члена или друга производного класса с частным наследованием известно, и восходящее преобразование из PrivatelyDer* в Base* (или PrivatelyDer& в Base& ) безопасно; приведение типов не требуется и не рекомендуется.
Однако пользователям PrivatelyDer следует избегать этого небезопасного преобразования, поскольку оно основано на закрытом/частном решении PrivatelyDer и может быть изменено без предварительного уведомления.
Как защищенное ( protected ) наследование связано с частным ( private ) наследованием?
Сходства: оба позволяют переопределять виртуальные функции в частном/защищенном базовом классе, и ни одно из них не утверждает, что производный класс является разновидностью базового класса.
Различия: защищенное наследование позволяет производным классам производных классов знать о наследственной связи. Таким образом, ваши внуки действительно узнают о деталях вашей реализации. Это имеет как преимущества (позволяет производным классам защищенного производного класса использовать связь с защищенным базовым классом), так и затраты (защищенный производный класс не может изменить связи, не нарушив при этом последующие производные классы).
Защищенное наследование использует синтаксис : protected .
class Car : protected Engine < public: // . >;
Каковы правила доступа при частном и защищенном наследованиях?
Возьмем в качестве примера следующие классы:
class B < /*. */ >; class D_priv : private B < /*. */ >; class D_prot : protected B < /*. */ >; class D_publ : public B < /*. */ >; class UserClass < B b; /*. */ >;
Ни один из производных классов не может получить доступ к чему-либо, что является private в B . В D_priv открытая и защищенная части B являются частными. В D_prot открытая и защищенная части B являются защищенными. В D_publ открытые части B являются открытыми, а защищенные части B – защищенными ( D_publ – это разновидность B ). Класс UserClass может получить доступ только к открытым частям B , что «изолирует» UserClass от B .
Чтобы сделать публичный член B публичным в D_priv или D_prot , укажите имя члена с префиксом B:: . Например, чтобы сделать член B::f(int, float) открытым в D_prot , вы должны сказать:
class D_prot : protected B < public: using B::f; // Примечание: не using B::f(int,float) >;
Наследование в C++: beginner, intermediate, advanced
В этой статье наследование описано на трех уровнях: beginner, intermediate и advanced. Expert нет. И ни слова про SOLID. Честно.
Beginner
Что такое наследование?
Наследование является одним из основополагающих принципов ООП. В соответствии с ним, класс может использовать переменные и методы другого класса как свои собственные.
Класс, который наследует данные, называется подклассом (subclass), производным классом (derived class) или дочерним классом (child). Класс, от которого наследуются данные или методы, называется суперклассом (super class), базовым классом (base class) или родительским классом (parent). Термины “родительский” и “дочерний” чрезвычайно полезны для понимания наследования. Как ребенок получает характеристики своих родителей, производный класс получает методы и переменные базового класса.
Наследование полезно, поскольку оно позволяет структурировать и повторно использовать код, что, в свою очередь, может значительно ускорить процесс разработки. Несмотря на это, наследование следует использовать с осторожностью, поскольку большинство изменений в суперклассе затронут все подклассы, что может привести к непредвиденным последствиям.
В этом примере, метод turn_on() и переменная serial_number не были объявлены или определены в подклассе Computer . Однако их можно использовать, поскольку они унаследованы от базового класса.
Важное примечание: приватные переменные и методы не могут быть унаследованы.
#include using namespace std; class Device < public: int serial_number = 12345678; void turn_on() < cout private: int pincode = 87654321; >; class Computer: public Device <>; int main() < Computer Computer_instance; Computer_instance.turn_on(); cout
Типы наследования
В C ++ есть несколько типов наследования:
- публичный ( public )- публичные ( public ) и защищенные ( protected ) данные наследуются без изменения уровня доступа к ним;
- защищенный ( protected ) — все унаследованные данные становятся защищенными;
- приватный ( private ) — все унаследованные данные становятся приватными.
Для базового класса Device , уровень доступа к данным не изменяется, но поскольку производный класс Computer наследует данные как приватные, данные становятся приватными для класса Computer .
#include using namespace std; class Device < public: int serial_number = 12345678; void turn_on() < cout >; class Computer: private Device < public: void say_hello() < turn_on(); cout >; int main() < Device Device_instance; Computer Computer_instance; coutКласс Computer теперь использует метод turn_on() как и любой приватный метод: turn_on() может быть вызван изнутри класса, но попытка вызвать его напрямую из main приведет к ошибке во время компиляции. Для базового класса Device , метод turn_on() остался публичным, и может быть вызван из main .
Конструкторы и деструкторы
В C ++ конструкторы и деструкторы не наследуются. Однако они вызываются, когда дочерний класс инициализирует свой объект. Конструкторы вызываются один за другим иерархически, начиная с базового класса и заканчивая последним производным классом. Деструкторы вызываются в обратном порядке.
Важное примечание: в этой статье не освещены виртуальные десктрукторы. Дополнительный материал на эту тему можно найти к примеру в этой статье на хабре.
#include using namespace std; class Device < public: // constructor Device() < cout // destructor ~Device() < cout >; class Computer: public Device < public: Computer() < cout ~Computer() < cout >; class Laptop: public Computer < public: Laptop() < cout ~Laptop() < cout >; int main()Конструкторы: Device -> Computer -> Laptop .
Деструкторы: Laptop -> Computer -> Device .Множественное наследование
Множественное наследование происходит, когда подкласс имеет два или более суперкласса. В этом примере, класс Laptop наследует и Monitor и Computer одновременно.
#include using namespace std; class Computer < public: void turn_on() < cout >; class Monitor < public: void show_image() < cout >; class Laptop: public Computer, public Monitor <>; int main()Проблематика множественного наследования
Множественное наследование требует тщательного проектирования, так как может привести к непредвиденным последствиям. Большинство таких последствий вызваны неоднозначностью в наследовании. В данном примере Laptop наследует метод turn_on() от обоих родителей и неясно какой метод должен быть вызван.
#include using namespace std; class Computer < private: void turn_on() < cout >; class Monitor < public: void turn_on() < cout >; class Laptop: public Computer, public Monitor <>; int main() < Laptop Laptop_instance; // Laptop_instance.turn_on(); // will cause compile time error return 0; >Несмотря на то, что приватные данные не наследуются, разрешить неоднозначное наследование изменением уровня доступа к данным на приватный невозможно. При компиляции, сначала происходит поиск метода или переменной, а уже после — проверка уровня доступа к ним.
Intermediate
Проблема ромба
Проблема ромба (Diamond problem)- классическая проблема в языках, которые поддерживают возможность множественного наследования. Эта проблема возникает когда классы B и C наследуют A , а класс D наследует B и C .
К примеру, классы A , B и C определяют метод print_letter() . Если print_letter() будет вызываться классом D , неясно какой метод должен быть вызван — метод класса A , B или C . Разные языки по-разному подходят к решению ромбовидной проблем. В C ++ решение проблемы оставлено на усмотрение программиста.
Ромбовидная проблема — прежде всего проблема дизайна, и она должна быть предусмотрена на этапе проектирования. На этапе разработки ее можно разрешить следующим образом:
- вызвать метод конкретного суперкласса;
- обратиться к объекту подкласса как к объекту определенного суперкласса;
- переопределить проблематичный метод в последнем дочернем классе (в коде — turn_on() в подклассе Laptop ).
#include using namespace std; class Device < public: void turn_on() < cout >; class Computer: public Device <>; class Monitor: public Device <>; class Laptop: public Computer, public Monitor < /* public: void turn_on() < cout // uncommenting this function will resolve diamond problem */ >; int main() < Laptop Laptop_instance; // Laptop_instance.turn_on(); // will produce compile time error // if Laptop.turn_on function is commented out // calling method of specific superclass Laptop_instance.Monitor::turn_on(); // treating Laptop instance as Monitor instance via static cast static_cast( Laptop_instance ).turn_on(); return 0; >
Если метод turn_on() не был переопределен в Laptop, вызов Laptop_instance.turn_on() , приведет к ошибке при компиляции. Объект Laptop может получить доступ к двум определениям метода turn_on() одновременно: Device:Computer:Laptop.turn_on() и Device:Monitor:Laptop.turn_on() .
Проблема ромба: Конструкторы и деструкторы
Поскольку в С++ при инициализации объекта дочернего класса вызываются конструкторы всех родительских классов, возникает и другая проблема: конструктор базового класса Device будет вызван дважды.
#include using namespace std; class Device < public: Device() < cout >; class Computer: public Device < public: Computer() < cout >; class Monitor: public Device < public: Monitor() < cout >; class Laptop: public Computer, public Monitor <>; int main()
Виртуальное наследование
Виртуальное наследование (virtual inheritance) предотвращает появление множественных объектов базового класса в иерархии наследования. Таким образом, конструктор базового класса Device будет вызван только единожды, а обращение к методу turn_on() без его переопределения в дочернем классе не будет вызывать ошибку при компиляции.
#include using namespace std; class Device < public: Device() < cout void turn_on() < cout >; class Computer: virtual public Device < public: Computer() < cout >; class Monitor: virtual public Device < public: Monitor() < cout >; class Laptop: public Computer, public Monitor <>; int main()
Примечание: виртуальное наследование в классах Computer и Monitor не разрешит ромбовидное наследование если дочерний класс Laptop будет наследовать класс Device не виртуально ( class Laptop: public Computer, public Monitor, public Device <>; ).
Абстрактный класс
В С++, класс в котором существует хотя бы один чистый виртуальный метод (pure virtual) принято считать абстрактным. Если виртуальный метод не переопределен в дочернем классе, код не скомпилируется. Также, в С++ создать объект абстрактного класса невозможно — попытка тоже вызовет ошибку при компиляции.
#include using namespace std; class Device < public: void turn_on() < cout virtual void say_hello() = 0; >; class Laptop: public Device < public: void say_hello() < cout >; int main() < Laptop Laptop_instance; Laptop_instance.turn_on(); Laptop_instance.say_hello(); // Device Device_instance; // will cause compile time error return 0; >
Интерфейс
С++, в отличии от некоторых ООП языков, не предоставляет отдельного ключевого слова для обозначения интерфейса (interface). Тем не менее, реализация интерфейса возможна путем создания чистого абстрактного класса (pure abstract class) — класса в котором присутствуют только декларации методов. Такие классы также часто называют абстрактными базовыми классами (Abstract Base Class — ABC).
#include using namespace std; class Device < public: virtual void turn_on() = 0; >; class Laptop: public Device < public: void turn_on() < cout >; int main() < Laptop Laptop_instance; Laptop_instance.turn_on(); // Device Device_instance; // will cause compile time error return 0; >
Advanced
Несмотря на то, что наследование — фундаментальный принцип ООП, его стоит использовать с осторожностью. Важно думать о том, что любой код который будет использоваться скорее всего будет изменен и может быть использован неочевидным для разработчика путем.
Наследование от реализованного или частично реализованного класса
Если наследование происходит не от интерфейса (чистого абстрактного класса в контексте С++), а от класса в котором присутствуют какие-либо реализации, стоит учитывать то, что класс наследник связан с родительским классом наиболее тесной из возможных связью. Большинство изменений в классе родителя могут затронуть наследника что может привести к непредвиденному поведению. Такие изменения в поведении наследника не всегда очевидны — ошибка может возникнуть в уже оттестированом и рабочем коде. Данная ситуация усугубляется наличием сложной иерархии классов. Всегда стоит помнить о том, что код может изменяться не только человеком который его написал, и пути наследования очевидные для автора могут быть не учтены его коллегами.
В противовес этому стоит заметить что наследование от частично реализованных классов имеет неоспоримое преимущество. Библиотеки и фреймворки зачастую работают следующим образом: они предоставляют пользователю абстрактный класс с несколькими виртуальными и множеством реализованных методов. Таким образом, наибольшее количество работы уже проделано — сложная логика уже написана, а пользователю остается только кастомизировать готовое решение под свои нужды.
Интерфейс
Наследование от интерфейса (чистого абстрактного класса) преподносит наследование как возможность структурирования кода и защиту пользователя. Так как интерфейс описывает какую работу будет выполнять класс-реализация, но не описывает как именно, любой пользователь интерфейса огражден от изменений в классе который реализует этот интерфейс.
Интерфейс: Пример использования
Прежде всего стоит заметить, что пример тесно связан с понятием полиморфизма, но будет рассмотрен в контексте наследования от чистого абстрактного класса.
Приложение выполняющее абстрактную бизнес логику должно настраиваться из отдельного конфигурационного файла. На раннем этапе разработки, форматирование данного конфигурационного файла до конца сформировано не было. Вынесение парсинга файла за интерфейс предоставляет несколько преимуществ.
Отсутствие однозначности касательно форматирования конфигурационного файла не тормозит процесс разработки основной программы. Два разработчика могут работать параллельно — один над бизнес логикой, а другой над парсером. Поскольку они взаимодействуют через этот интерфейс, каждый из них может работать независимо. Данный подход облегчает покрытие кода юнит тестами, так как необходимые тесты могут быть написаны с использованием мока (mock) для этого интерфейса.
Также, при изменении формата конфигурационного файла, бизнес логика приложения не затрагивается. Единственное чего требует полный переход от одного форматирования к другому — написания новой реализации уже существующего абстрактного класса (класса-парсера). В дальнейшем, возврат к изначальному формату файла требует минимальной работы — подмены одного уже существующего парсера другим.
Заключение
Наследование предоставляет множество преимуществ, но должно быть тщательно спроектировано во избежание проблем, возможность для которых оно открывает. В контексте наследования, С++ предоставляет широкий спектр инструментов который открывает массу возможностей для программиста.