Перед началом снова вынужден констатировать, что этого вам традиционно не расскажет ни один учебник в мире или блогер-программист. Так как никто не учит математическим сущностям программирования, все учат только синтаксисам языков и повторяют дич официальщины. А ведь одну математическую сущность можно в миллионах языков записать миллиардом разных синтаксисов! И вы будете изучать миллион языков? Может лучше изучить одну сущность?
Единая природа наследования, интерфейсов, абстрактных классов
Самая главная мысль моя в данной статье, что у наследования, интерфейсов и абстрактных классов абсолютно одна природа. Заметьте, никто в мире вам этого не говорит! А это 100 процентов так.
Смысл в том здесь, что всего существует три вида наследования, реализации интерфейсов и абстрактных классов, но при этом данные три вида могут в разных ситуациях давать четыре разных сочетания, в итоге получается всего четыре случая наследования, реализации интерфейсов и абстрактных классов.
Пока вам этого всего непонятно, но здесь нет ничего сложного, если заглянуть "под капот". Давайте проясним: что такое интерфейсы, абстрактные классы и наследование?
Интерфейс - по своей природе это неполноценный чертеж (рисунок) объекта с пустыми коробками-ссылками методов, по интерфейсу нельзя построить ни одной детали объекта, то есть рисунок машины с техзаданием на чертежи является примером интерфейса, рисунок самолета является интерфейсом, где от крыльев и хвоста самолета есть наброски на бумаге и техзадание, но нет полного готового чертежа ни одной детали, поэтому по интерфейсу не построить готовый объект.
Поняли? Теперь давайте узнаем об абстрактных классах. Что это? Это очень похожие на интерфейс сущности.
Абстрактные классы - по своей природе это неполноценные чертежи объектов, где часть деталей начерчена полноценно, а часть в виде рисунков и набросков. По абстрактному классу не построить ни одного объекта, то есть чертеж машины без колес и без двигателя и без корпуса является примером абстрактного класса, здесь недостающие детали чертежа выполнены в виде рисунков и техзадания на чертеж. Чертеж самолета без крыльев и хвоста, которые существуют в виде рисунков, является абстрактным классом. Абстрактный класс является подвидом интерфейса, то есть более полноценным чертежом объекта.
А что такое наследование "под капотом"?
Наследование - это включение в готовый чертеж объекта интерфейсов, абстрактных классов или других готовых чертежей объектов (предков, базовых классов) с целью либо переопределения поведения данных объектов (1), либо копирования поведения данных объектов (2), либо двойного переопределения поведения наследующих объектов (3), либо частично копирования с переопределением поведения у наследующих объектов (4).
Запомните данные 4 случая наследования и реализации интерфейсов и абстрактных классов, вам о них никто в мире не расскажет, а далее я вам их подробно опишу, чтоб вы навсегда запомнили эти вечные понятия.
Случай первый: переопределение поведения объектов при наследовании
Здесь все просто и элементарно, у вас для наследования имеются либо "голый" интерфейс, либо частично реализованный абстрактный класс, либо у вас имеется базовый класс для наследования именно с виртуальными методами. Пропаганда не называет реализацию интерфейсов и абстрактных классов наследованием, а я для вас буду называть все данные процессы видами наследования для вашего понимания.
Смысл в данном случае, что у вас всегда в наследуемом одном из трех видов объекте всегда должны присутствовать пустые методы-функции или изменяющиеся методы (виртуальные), которых официальщина везде называет по-разному, в интерфейсах на С++ они пустые методы называют "чисто виртуальными" методами, переопределяющиеся непустые методы они называют "виртуальными" методами, пустые методы в абстрактных классах они называют "абстрактными" методами. То есть вам придумали кучу путанных названий, но при данном (1) случае наследования всегда присутствуют пустые "чисто виртуальные" методы в интерфейсах и абстрактных классах или переопределяющиеся непустые "виртуальные" методы в базовых классах! Запомните навсегда!
virtual int Method1 (int a, double b) { a = 5; return a; }
переопределяющийся "виртуальный" метод в C#
int Method1 (int a, double b);
пустой "чисто виртуальный" метод в C++
Ну как? Вам стал понятен смысл случая первого? Смысл в том, что в чертеже любого из трех начальных объектов для наследования (интерфейсов, абстрактных классов, базовых классов) у вас обязательно присутствуют методы для переопределения их поведения в наследующем объекте, то есть пустые "чисто виртуальные" или "абстрактные" методы у интерфейсов и абстрактных классов, переопределяющиеся "виртуальные" методы у базовых классов (типов) обычного наследования. Понятно?
В данном случае вы всегда переопределяете поведение у наследующего объекта, будь то интерфейс объекта с пустым поведением, абстрактный класс с частично пустым поведением или базовый класс с переопределяющимися заполненными стартовым поведением методами. Надеюсь, что я понятно пояснил суть... Приступаем ко второму случаю.
Случай второй: копирование поведения наследуемого объекта без его переопределения
Здесь также все просто. У вас есть полноценный чертеж объекта (базовый класс наследования) и в любом случае при наследовании вы только копируете поведение этого наследуемого объекта, почти никак не переопределив его методы и не изменив их поведения, а также вы не добавляете новых методов при наследовании, то есть не происходит расширяющего типа наследования.
Если у вас нет в базовом объекте наследования пустых "чисто виртуальных" или "виртуальных" переопределяющихся методов, то вы и не можете переопределить поведение при наследовании, а можете только скопировать прежние и добавить к ним новые методы, а это будет уже расширяющее наследование из следующих двух случаев. В данном случае есть только копирование "невиртуальных" методов базового класса без расширения и вставка их в наследующий объект.
Во втором в данном случае наследования интерфейсы и абстрактные классы отсутствуют и не участвуют, так как у них всегда есть переопределяющие или пустые методы, интерфейс и абстрактный класс вы всегда "переопределяете", записывая вместо пустого метода какое-то поведение, а здесь объектом наследования является базовый класс с обычными "невиртуальными" методами, которые копируются при наследовании и вставляются в потомка, а не переопределяются.
В итоге во втором случае у наследующего объекта поведение полностью переходит из базового класса (типа) объекта, он становится почти его клоном, если мы не добавляем никаких расширяющих методов при наследовании. Понятно теперь?
Такой случай копирующего наследования нужен в тех случаях, когда вы ничего не хотите создавать нового, у вас есть готовый рабочий тип объекта и вам нужно всю логику его работы операторов и методов просто скопировать или передать в свой объект. Тогда ваш объект получит поведение наследуемого объекта почти целиком.
Случай третий: двойное переопределение поведения объекта при наследовании или интерфейсах
Этот случай подразумевает одновременно расширяющее наследование совместно с переопределением базового класса или с реализацией интерфейса или абстрактного класса, то есть тут обязательно в базовом классе, интерфейсе или абстрактном классе должны присутствовать "чисто виртуальные" или "виртуальные" методы, которые будут переопределены поведением в наследующем классе и к ним еще будут добавлены новые расширяющие методы наследующего класса, которые также переопределяют поведение у наследующего объекта по сравнению со стартовым.
Если коротко, то в этом случае вы переопределяете интерфейсы, абстрактные классы и базовые классы и плюс еще расширяете их добавлением новых методов в наследующий класс, в итоге у вас получается двойное переопределение поведения при таком случае наследования! Понятно объяснил? Вроде все просто.
Третий случай наследования подходит, когда вы хотите создавать почти полностью уникальные типы объектов и при этом спокойно их закидывать внутрь обобщенных классов и методов, чтоб все стандартные и другие операторы и методы без проблем работали с вашим уникальным объектом, чтоб его везде принимали все сторонние методы, объекты и никто не отказывал в доступе.
Другими словами, такое наследование позволяет вам творить свое поведение у объектов и у них не будет проблем с подсоединением к разным операторам и методам, то есть будет полная свобода и огромная власть, особенно в сочетании с применением обобщений и таким наследованием.
Позже вам расскажу о фантастических возможностях применения обобщений совместно с данными четырьмя типами наследования, а пока изучим четвертый тип наследования.
Случай четвертый: частично копирование и частично переопределение поведения объектов
Данный случай от второго случая простого копирования отличается добавлением в наследующий класс (тип) новых методов поведения или добавляется расширяющее наследование к копирующему наследованию.
В итоге копирующее наследование базового класса (без интерфейсов и абстрактных классов) переносит поведение стартового объекта в наследующий, а расширяющее наследование добавлением новых методов в наследующий класс переопределяет его поведение в сравнении с базовым классом и делает его отчасти уникальным.
.....................................................................................................................................................
Напоследок запомните главные отличия интерфейсов и абстрактных классов от базовых классов в наследовании. Базовый класс с "виртуальными" или без них методами всегда является полноценным чертежом объекта, а у интерфейсов и абстрактных классов всегда есть хотя бы один пустой "чисто виртуальный" метод, который не позволяет по этим чертежам создавать объекты. Если у вас есть в чертежах объекта (в классах) наброски с рисунками хотя бы одной детали объекта - то это абстрактный класс или интерфейс!
Наконец, изложил вам основные вечные принципы наследования в любых языках программирования, это была сверхважная статья и надеюсь сделать для вас таких еще много. Подписывайтесь и ставьте лайки, не молчите, извиняйте меня и Дзен, что вместо рисунков пишу только текстовые примеры большого шрифта. Таких примеров буду делать очень много для замены картинок.