Pull to refresh

Comments 62

Термины “родительский” и “дочерний” чрезвычайно полезны для понимания наследования. Как ребенок получает характеристики своих родителей, производный класс получает методы и переменные базового класса.

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

В beginner я старалась подать информацию кратко и просто, а ваше определение, на мой взгляд, уже подбирается как к advanced, так и к смежным к наследованию понятиям. Тем не менее, вы дали хорошее и куда более полное определение термину и я с ним согласна.
И стратегии к примеру, в смысле шаблон проектирование, на базе шаблонов.
Ошибка:
«Поскольку в С++ при инициализации объекта дочернего класса вызываются конструкторы всех родительских классов, возникает и другая проблема: конструктор базового класса Device будет вызван дважды.»
class Laptop: public Computer, public Monitor {};

у объекта Laptop будет 2 разных объекта Device. И для каждого будет вызван конструктор.
У объекта Laptop будет один subobject (к сожалению не знаю точного перевода термина) класса Computer и один subobject класса Monitor, а те в свою очередь будут иметь по subobject`у класса Device. Как известно,
при инициализации объекта дочернего класса вызываются конструкторы всех родительских классов

и как результат
возникает и другая проблема: конструктор базового класса Device будет вызван дважды
мне всё равно не понятно, почему вы пишите в единственном числе о базовом классе. будут два объекта, два раза вызовется конструкторы. в чем проблема? в том что вы хотели один объект Device? ну так для этого virtual наследование и изобрели.

«возникает и другая проблема: конструктор базового класса Device будет вызван дважды.» в контексте вашего кода — «конструктор базового класса Device будет вызван дважды.» для _разных_ объектов. и «проблемой» это не является. «проблема» в наличии этих двух объектов.
Ну да, про виртуальный деструктор ни слова. Для начинающего это ещё та мина замедленного действия.
Да, вы абсолютно правы. Честно говоря, материала обрабатывала много, и про них просто забыла. Завтра статью дополню, спасибо
Наследование без виртуальных функций кое-как объяснили. А вот зачем нужны виртуальные функции и как их использовать с указателями на базовый класс — умолчали. Сразу перешли ко множественному наследованию.
Фабрика классов где?
То, что вы описываете, по моему мнению вплотную подходит к понятию «полиморфизм». Поскольку эта статья, как исходит из названия, о наследовании, я старалась не освещать смежные понятия
Поскольку эта статья, как исходит из названия, о наследовании, я старалась не освещать смежные понятия

«Не освещать» — это вовсе не значит «избегать любых упоминаний». :)
В тексте бы очень пригодилось кратенькое отступление в стиле «виртуальные функции и тонкости их использования — это о полиморфизме, поэтому их разберем в другой статье».
Может вы и правы, впрочем, я не уверена. Для достижения консенсуса напишу то что вы предлагаете тут, в комментарии — я действительно собираюсь написать про полиморфизм и то, о чем вы говорите там будет:)
Мне больше нравится рассматривать тип данных как некоторое множество объектов в собщими свойствами. Тогда потомки есть подмножество данного множества. То есть выбираются те элементы, которые имеют необходимые дополнительные свойства.
Таким образом, наследование есть сужение множества значений. Становится понятным, почему в объект потомка нельзя поместить объект родителя, но наоборот можно.
Но ведь наследование и есть помещение объекта-родителя в потомка. Или имеется ввиду относительно последовательности конструирования?
Нет. Нельзя (то есть крайне не рекомендуется) поместить объект родителя в потомка, потому что все свойства потомка, которых нет в родителе, останутся непроинициализированными, и, соответственно, результат их использования непредсказуем.
Отвлеченно:
image
  • Пусть Y — множество (класс-родитель), X — его подмножество (класс-потомок).
  • Существует объект (d) множества Y, которое не принадлежит X.
  • Любой объект (r, e, a) множества X, которое принадлежит Y.

Очень странные схемы вы рисуете. Класс-потомок наследует часть множества родителя исключая приватные поля — собственно то самое множество d. У потомка есть своё множество о которых гарантированно ничего не известно классу родителю — свои приватные, пубичные и переопределенные методы. Учитывая, что даже структурно класс-потомок ссылается на виртуальную таблицу методов — все же родитель помещается в потомка и из потомка же триггерит конструирование родителя, а не наоборот. Или вы имели ввиду вот такую ситуацию с указателями на объекты
Derive * x = new Base();?

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

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

И наследование не является основопологающей идеей ООП. В первой версии SmallTalk его, кстати, вообще не было.
Я сожалею о том, что вас огорчает то, что эта статья о наследовании. Также, если под LSP вы подразумеваете Liskov substitution principle, то смею подчеркнуть, что в статье не упомянуты и SRP, OCP, ISP, DIP, зато еще во вступлении сказано, что в статье нет ни слова про SOLID.

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

Напоследок хочу сказать, что наследование все же принято считать одним из основополагающих принципов ООП (автором того же 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
Принципиальны не статьи в википедии, но библиография к статьям.
Воббще-то, я считаю, что статья замечательная. Невозможно уместить в рамках статьи объемы энциклопедии. А рекомендации по использованию наследования — это тема отдельной статьи, возможно даже вашей.
UFO just landed and posted this here
Не уверен, что это можно назвать наследованием. А вот инкапсуляцией — вполне можно.

На счёт наследования в Си уже ответили выше.
Подменять понятия и выдавать ложные фразы пытаясь прикрыть их громким именем "Автора SOLID", абсолютно не разбираясь в теме, не может быть хорошо.
Нужно различать статьи которые покрывают лишь часть материала, и статьи которые откровенно вредят.

Абсолютно согласен. Давайте почитаем источник 1995 года:
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 ), иначе его парадигма ничем бы не отличалась от уже существующих приёмов написания кода, хоть в той же Симуле.

Вобщем печально что изначальные идеи ООП утерялись в глубинах истории.
Слушайте, я не знаю какие смыслы в термин ООП вкладывал Алан Кей. Но я начинал программировать на языках, про некоторые из которых вы, вполне вероятно, даже не слышали и в них не было даже намёка на ООП, классы и т.п. И когда в мой мир пришла парадигма ООП (а конкретно для меня она началась с Borland TurboPascal 5.5) это потребовало очень серьёзной перестройки мышления. Потому что это действительно была принципиально иная парадигма. И она вполне корректно описана в той же Википедии (и, кстати, там в определении ООП от Алана Кея п.6 значится именно наследование).
А если следовать вашей логике (и слегка довести её до абсурда — простите, профдеформация), то получается что даже обычный Бейсик вполне себе объектный язык, если я могу подключить к нему какую-нибудь библиотеку для работы с сообщениями — ведь в этот момент у него появится 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++ не разбираюсь )
Да, вы правы, исправила буквально за минуту до того как вы написали:)
UFO just landed and posted this here

Более узкое понимание наследования. Т.е. наследуется только то, что "видно" снаружи или внутри класса.

UFO just landed and posted this here
По большому счету, в Beginner я многое отбросила в пользу краткости и ясности формулировки. Также я старалась не руководствоваться предположениями в духе «а вот тут можно подумать что», потому как размер статьи (и без того огромный) вырос бы до невероятных масштабов. Я понимаю вашу точку зрения, но мне кажется для Beginner написано достаточно
UFO just landed and posted this here
Sign up to leave a comment.

Articles