Комментарии 62
Термины “родительский” и “дочерний” чрезвычайно полезны для понимания наследования. Как ребенок получает характеристики своих родителей, производный класс получает методы и переменные базового класса.
Некорректная аналогия. Хотя технически в С++ возможна эмуляция реализации подобного наследования, это считается крайне дурным тоном.
Вме-таки наследование в ООП не про отношения родитель-ребенок, а про классификацию объектов, когда объект порожденного наследованием класса вклюяает в себя все свойства и поведение базового класса, добавляя к нему деталей.
По существу объект порожденного класса является одновременно и объектом базового класса. Это позволяет, например, в одной коллекции хранить объекты разных классов, порожденных от общего, и обрабатывать их единообоазно.
«Поскольку в С++ при инициализации объекта дочернего класса вызываются конструкторы всех родительских классов, возникает и другая проблема: конструктор базового класса Device будет вызван дважды.»
class Laptop: public Computer, public Monitor {};
у объекта Laptop будет 2 разных объекта Device. И для каждого будет вызван конструктор.
при инициализации объекта дочернего класса вызываются конструкторы всех родительских классов
и как результат
возникает и другая проблема: конструктор базового класса Device будет вызван дважды
«возникает и другая проблема: конструктор базового класса Device будет вызван дважды.» в контексте вашего кода — «конструктор базового класса Device будет вызван дважды.» для _разных_ объектов. и «проблемой» это не является. «проблема» в наличии этих двух объектов.
Поскольку эта статья, как исходит из названия, о наследовании, я старалась не освещать смежные понятия
«Не освещать» — это вовсе не значит «избегать любых упоминаний». :)
В тексте бы очень пригодилось кратенькое отступление в стиле «виртуальные функции и тонкости их использования — это о полиморфизме, поэтому их разберем в другой статье».
Таким образом, наследование есть сужение множества значений. Становится понятным, почему в объект потомка нельзя поместить объект родителя, но наоборот можно.
Отвлеченно:

- Пусть Y — множество (класс-родитель), X — его подмножество (класс-потомок).
- Существует объект (d) множества Y, которое не принадлежит X.
- Любой объект (r, e, a) множества X, которое принадлежит Y.
Очень странные схемы вы рисуете. Класс-потомок наследует часть множества родителя исключая приватные поля — собственно то самое множество d. У потомка есть своё множество о которых гарантированно ничего не известно классу родителю — свои приватные, пубичные и переопределенные методы. Учитывая, что даже структурно класс-потомок ссылается на виртуальную таблицу методов — все же родитель помещается в потомка и из потомка же триггерит конструирование родителя, а не наоборот. Или вы имели ввиду вот такую ситуацию с указателями на объекты
Derive * x = new Base();
?
Думаю для начинающих, которые пойдут лепить его где надо и где не надо(то есть везже где заметят похожий, на из взгляд, код) статья скорее вредна чем полезна.
И наследование не является основопологающей идеей ООП. В первой версии SmallTalk его, кстати, вообще не было.
Смею надеяться что не все начинающие пойдут лепить наследование куда не надо, но если это все же произойдет, то я в этом буду виновата в той же мере, что и, допустим, википедия.
Напоследок хочу сказать, что наследование все же принято считать одним из основополагающих принципов ООП (автором того же SOLID, к примеру)
принято считать одним из основополагающих принципов ООП (автором того же SOLID, к примеру)
Не говорил Дядя Боб такого, он высмеивал эту терминологию упоминая что «Инкапсуляция Наследование и Полимофизм» достижимы и в Си, который вроде как не ООП.
Если вы про книгу «Clean Architecture»(«Чистая Архитектура»), то вам определенно стоит внимательно её перечитать. Мне жаль тех новичков кто наткнется на вашу статью, потому что умением фильтровать информацию они, к сожалению, пока что особо не обладают.
Смею надеяться что не все начинающие пойдут лепить наследование куда не надо, но если это все же произойдет, то я в этом буду виновата в той же мере, что и, допустим, википедия.
В русской Википедии тоже всё плохо. Смысл тащить это ещё и на Хабр?
что в статье не упомянуты и SRP, OCP, ISP, DIP,
Если вы обдумаете эти принципы, вы поймете почему я упомянул именно LSP
P.s.
(автором того же SOLID, к примеру)
Я конечно понял что вы про Дядюшку Боба, но он не автор принципов из SOLID, он лишь аббревиатуру красивую придумал
Касательно автора принципов SOLID, а также аббревиатуры:
en.wikipedia.org/wiki/Robert_C._Martin
en.wikipedia.org/wiki/SOLID
Принципиальны не статьи в википедии, но библиография к статьям.
Воббще-то, я считаю, что статья замечательная. Невозможно уместить в рамках статьи объемы энциклопедии. А рекомендации по использованию наследования — это тема отдельной статьи, возможно даже вашей.
На счёт наследования в Си уже ответили выше.
Подменять понятия и выдавать ложные фразы пытаясь прикрыть их громким именем "Автора SOLID", абсолютно не разбираясь в теме, не может быть хорошо.
Нужно различать статьи которые покрывают лишь часть материала, и статьи которые откровенно вредят.
tinyurl.com/84emx
Я ничего не говорил о вашей компетентности, так что прошу не высказываться о моей.
Поскольку мы с вами приходим к диаметрально противоположным выводам читая один и тот же текст, дальнейшее обсуждение темы кажется мне бессмысленным. Если вы горите желанием продолжить дискуссию и ваши аргументы не связаны с вашим личным отношением к наследованию или с сочувствием новичкам — можете написать мне лично, толку разводить неплодотворную полемику в комментариях я не вижу
Во-первых, спасибо:) Во-вторых, тема применения ООП принципов в С поднималась не раз, к примеру OOP with ANSI-C тут, и тот же Мартин в Clean Architecture. По большому счету, написана и масса статей, и их можно найти просто погуглив
Во-вторых, тема применения ООП принципов в С поднималась не раз, к примеру OOP with ANSI-C тут, и тот же Мартин в Clean Architecture
Перечитайте, пожалуйста, моё предыдущее сообщение, а потом книгу. Потому что речь там, как я уже писал, идёт как раз о том, что «Инкапсуляция, наследование и полимофризм» это не принципы ООП.
Максимум что дало ООП — чуть более удобную реализацию наследования, остальные два «принципа» вообще мимо.
1.
Инкапсуляция упоминается как часть определения ОО потому, что языки
ОО поддерживают простой и эффективный способ инкапсуляции данных
и функций. Как результат, есть возможность очертить круг связанных
данных и функций. За пределами круга эти данные невидимы и доступны
только некоторые функции. Воплощение этого понятия можно наблюдать
в виде приватных членов данных и общедоступных членов-функций класса.
Эта идея определенно не уникальная для ОО. Например, в языке C имеется
превосходная поддержка инкапсуляции. Рассмотрим простую программу
на C:
…
То есть языки, заявляющие о поддержке OO, фактически ослабили
превосходную инкапсуляцию, некогда существовавшую в C.
2.
То есть можно сказать, что некоторая разновидность наследования у нас
имелась задолго до появления языков ОО. Впрочем, это утверждение не
совсем истинно. У нас имелся трюк, хитрость, не настолько удобный, как
настоящее наследование.
…
Справедливости ради следует отметить, что языки ОО действительно
сделали маскировку структур данных более удобной, хотя это и не совсем
новая особенность.
Итак, мы не можем дать идее ОО ни одного очка за инкапсуляцию и можем
дать лишь пол-очка за наследование.
3.
Была ли возможность реализовать полиморфное поведение до появления
языков ОО? Конечно!
Но главное, что хотелось бы видеть в статьях о наследовании:
1. Что есть и другие способы переиспользования кода (и если вам часто приходится использовать наследование, возможно вам следует задуматься о наличии проблем в дизайне вашей системы). Жесткая фиксация иерархии типов не есть хорошо по многим причинам.
2. Что делать наследование опасно, и нужно задуматься о том, к каким проблемам это может привести, задуматься о совместимости типов, это, как я уже упомянул, LSP и контракты.
Вы можете сослаться на то, что это не влезло в статью, но тогда я в принципе не понимаю, что вы хотели донести, т.к. нету ни грамотного описания понятия, ни грамотного описания как его использовать.
И наследование не является основопологающей идеей ООП.
Наследование является одним из четырёх основополагающих принципов ООП.
Наследование является одним из четырёх основополагающих принципов ООП.
То есть, получается, что Си Объектно-Ориентированный язык, а первые версии SmallTalk — нет?)
Если обратиться к определнию Алана Кея, основополагающие идеи ООП — messaging, information hiding, late static binding.
Классы+инстансы классов != ООП.
В ином случае термин ООП просто не имеет смысла, потому что каждый понимат его по своему.
То есть, получается, что Си Объектно-Ориентированный язык, а первые версии SmallTalk — нет?)
Если в Си есть наследование, инкапсуляция, полиморфизм и абстракции — то да. Я последний раз писал на Си лет 25 назад, тогда этого там, насколько я помню, не было. Но возможно я просто не в курсе.
Про SmallTalk не скажу, не знаю. Опять же, насколько я помню он считается первым ООП языком (не считая Симулы-67) и в нём был тот самый messaging. Но если в нём не было наследования он, пожалуй, не полноценная реализация ЯООП.
Классы+инстансы классов != ООП
Если ООП = OOP — то да. Если ООП = OOD, то вполне
Если ООП = OOP — то да.
ООП = Объектно-Ориентированное Программирование. Термин который ввёл Алан Кей, бакалавр молекулярной биологии, и ввел он его позже чем появилась Симула.
Идеи, которые он вкладывал в это понятие — вовсе не «Наследование, инкаспуляция и полиморфизм»( wiki.c2.com/?AlanKaysDefinitionOfObjectOriented ), иначе его парадигма ничем бы не отличалась от уже существующих приёмов написания кода, хоть в той же Симуле.
Вобщем печально что изначальные идеи ООП утерялись в глубинах истории.
А если следовать вашей логике (и слегка довести её до абсурда — простите, профдеформация), то получается что даже обычный Бейсик вполне себе объектный язык, если я могу подключить к нему какую-нибудь библиотеку для работы с сообщениями — ведь в этот момент у него появится messaging и он станет «почти как SmallTalk».
Бейсик вполне себе объектный язык, если я могу подключить к нему какую-нибудь библиотеку для работы с сообщениями — ведь в этот момент у него появится messaging и он станет «почти как SmallTalk».
Вот мы и пришли к моменту что ООП это не только конкретный набор ограничений и не функционала языка, это ещё парадигма в которой можно писать а можно и не писать даже в тех языках что кличутся ООП.
Ну и да, мессаджинг утерян и сейчас лишь немного возврождается в Actor Model (Scala(Acca) / Erlang), либо даже в виде микросервисах, если подходить к ним грамотно.
Да и черт бы с ним с самим понятием, я не топлю что оно действительно нужно. Скорее даже, я не вижу смысла от него. Только если оно не нужно в изначальном виде, то в виде «Инкапсуляция наследование полиморфизм» и подавно. Есть на много более важные вещи, над которыми стоит задумываться.
А такую полезную штуку как Information Hiding можно рассматривать в отдельности от ООП, может хоть немного получится донести до людей что это сокрытие стейта, а не «заменить паблик поле геттером».
А вообще, в ООП от Алана Кея, был ещё один такой момент, как — «Всё — объект», наподобие клеток в организме, обменивающимися сообщениями, и именно эта часть, например, совсем не прижилась в современных языках программирования, если говорить именно о синтаксисе языка.
Вообще, не знаю что вы подразумевали под ООД, но могу предположиться что именно эти понятия, и то о чем вещал Девид Вест(«OOP is dead, long live OOD» / «Object Thinking»)
Но это не имеет никакого отношения к самой парадигме. Я, например, вообще — разработчик БД (казалось бы — где SQL и где ООП?!), но тем не менее мыслю во вполне объектной парадигме.
По поводу объектектного мышления / ООД — с ним тоже все отлично, по сути это лучшие практики из умершего ООП. Только наследование в них не входит.
Впрочем, думается мне, что вы это и без меня прекрасно понимаете, просто называете чутка по другому
Но наследование, таки, входит в «набор элементов ООП». Об этом даже стрелочка с треугольником на конце в соответствующих диаграммах говорит.
Потому что если наследования нет, то, практически автоматом, «выбывают» абстракции и полиморфизм. И что остаётся?
Но наследование, таки, входит в «набор элементов ООП»
Нет. Ещё раз, ООП это конкретный термин введенный конкретным человеком. Его определение хоть и изначально было размыто, но никаким наследованием и стрелочками там и не пахнет. Наследование возможно как с «ООП» которое вы предлагаете, так и без него. Типичное «наследование инкапсуляция и полиморфизм» реализуемы и без объектов в понимании «объект = инстантс класса», и по сути ничем не отличается от структурного программирования с сокрытием состояния в каком-то скоупе, взять ту же инкапсуляцию в Си.
если наследования нет, то, практически автоматом, «выбывают» абстракции и полиморфизм.
Да и пусть выбывают, эти штуки существуют как с объектами, так и без, поэтому основопологающими принципами ОО-парадигмы быть не могут в принципе. Оригинальный термин ООП был не о добавлении ключевого слова object/class в язык программирования, а о децентрализации, о том как эти объекты будут между собой общаться.
Если не так, то мне интересно, в чем вы видите отличия между объектно-ориентированной парадигмой и структурной, если инкапсуляция, наследование и полиморфизм возможны в обоих.
Вот так и со структурным и объектным. Всё дело в конкретной реализации.
А про определение Алана Кея я вам ещё вчера написал (см. п.6 в Википедии).
P.S. И да, я читал «Чистую архитектуру» (в том числе). И да, я прекрасно понимаю то, что там написано :)
Тут вот какое дело: любую (я подчёркиваю — любую!) программу, реализованную на любом (хоть ООП, хоть нет) ЯП можно реализовать и на ассемблере.
А кто спорит то?)
Все парадигмы это просто разные способы делать одни и те же вещи, где-то удобнее, где-то нет.
А про определение Алана Кея я вам ещё вчера написал (см. п.6 в Википедии).
Я ни в русской ни в Английской версии википедии не нашел упоминания определения от Алана Кея в п.6
Честно говоря, в русской википелии в п.6 вообщее какие-то вообще не связанные с ООП вещи и описание недостатков наследования и иерархии классов, никоим образом к Объектно-ориентированной парадигме не относящиеся. Мы можем сделать иерархию структур в том же Си, и можем писать на Объектно-ориентированном языке и не встретиться с такой проблемой если в нем не будет наследования.
Из вики
Объектно-ориентированное проектирование ориентируется на описание структуры проектируемой системы (приоритетно по отношению к описанию её поведения, в отличие от функционального программирования), то есть, фактически, в ответе на два основных вопроса:
Из каких частей состоит система;
В чём состоит ответственность каждой из её частей.
Конкретно этот кусок вообще не понятно как там оказался, когда мы пишем процедуры, или когда мы реализовываем инкапсуляцию в Си, да и вообще, в тру-ФП языках аля Хаскель, нам всё так же нужно думать из каких частей состоит система, и какие у этих частей ответственности
«Всё — объект», наподобие клеток в организме, обменивающимися сообщениями, и именно эта часть, например, совсем не прижилась в современных языках программирования, если говорить именно о синтаксисе языка.
На самом деле неважно, каким именно механизмом передаются сообщения: вызовом функции, посылкой сообщения, сигналами и слотами, событиями, семафорами. Важно, что при описании объектов как-то описывается воздействие одного на другой (условная посылка сообщения) и реакция другого на это "сообщение", т.е. на воздействие первого. И во всех современных ООП языках этот обмен сообщениями при помощи какого-либо механизма вполне реализован, пусть сам этот механизм не называется и не является сообщениями по форме и факту. Сообщения эти механизмы в различных ООП языках вполне себе эмулируют.
На самом деле неважно, каким именно механизмом передаются сообщения: вызовом функции, посылкой сообщения, сигналами и слотами, событиями, семафорами.
…
Сообщения эти механизмы в различных ООП языках вполне себе эмулируют.
Не совсем так. Понятие сообщений шире чем понятие вызова методов.
Отбросив момент что сообщения могут быть асинхронными, разница между ними и вызовом методов ещё и в том, что отправляя сообщение, отправителю не важно, и не нужно указывать кто конкретно будет его обрабатывать.
Эмуляция не обязана воспроизводить процесс полностью. Достаточно только интересующей части. Соответственно, каждый ООП программист реализует ту часть процесса передачи сообщений, которую посчитал достаточной для своей модели.
Причем большинство ООП языков вообще не реализуют конкретного метода передачи сообщений между объектами, отдавая этот процесс на откуп программистам. Все конкретные способы эмуляции передачи сообщений обычно мы получаем уже на уровне библиотек (VCL, QT). Как говорится, все в ваших руках.
когда в мой мир пришла парадигма ООП (а конкретно для меня она началась с Borland TurboPascal 5.5) это потребовало очень серьёзной перестройки мышления.
Кстати, мне правда интересно, что нового для вас внесла эта парадигма придя в виде классов и экземпляров классов? Ну то есть… В чем принципиальное отличие от того же процедурного программирования, что пришлось серьёзно перестраивать мышление?
Вообще, без принципов того же Алана Кея, даже то что сейчас называют ООП не сильно то отличается от старой доброй процедурщины( «храним данные и методы, которые меняют эти данные, в одном месте, потом вытягиваем данные через геттеры» )
Если исходить из семантики словосочетания, прячущегося за аббревиатурой ООП, то ООП — это про написание программ на основе модели, в основу которой положены объекты и их взаимодействие.
Все остальные определения — это подмена при описании ООП принципа инструментами, которые используются для реализации ООП.
Если исходить из семантики словосочетания, прячущегося за аббревиатурой ООП, то ООП — это про написание программ на основе модели, в основу которой положены объекты и их взаимодействие.
Это термин введенный конкретным человеком, хоть и весьма размытый. А вообще я не понимаю как это противоречит моим словам и определению Алана Кея. В основу модели программы ложатся объекты, которые взаимодействуют путем посылки сообщений. Разве нет?
"Предпочитайте композицию наследованию"
34 правило Александреску / Саттера.
В С++, класс в котором существует хотя бы один виртуальный метод принято считать абстрактным.
Если обратиться к первоисточникам, то:
An abstract class is a class that can be used only as a base class of some other class; no objects of an abstract class can be created except as subobjects of a class derived from it. A class is abstract if it has at least one pure virtual function.
Хотя я в C++ не разбираюсь )
Более узкое понимание наследования. Т.е. наследуется только то, что "видно" снаружи или внутри класса.
Наследование в C++: beginner, intermediate, advanced