Почему наследование всегда было бессмысленным

https://www.sicpers.info/2018/03/why-inheritance-never-made-any-sense/
  • Перевод
Есть три типа наследования.

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

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

Часто для наследования в ООП приводят контрпример отношений между квадратом и прямоугольником. Геометрически квадрат — это специализация прямоугольника: все квадраты — прямоугольники, но не все прямоугольники — квадраты. Все s в классе «Квадрат» являются прямоугольниками s, у которых длина равна ширине. Но в иерархии типов это отношение обратное: вы можете использовать прямоугольник везде, где используется квадрат (указав прямоугольник с одинаковой шириной и высотой), но нельзя использовать квадрат везде, где используется прямоугольник (например, вы не можете изменить длину и ширину).

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

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

Синяя книга по Smalltalk описывает наследование исключительно с точки зрения наследования реализации:
«Подкласс определяет, что все его экземпляры будут, за исключением явно указанных отличий, такими же, как экземпляры другого класса, называемого его суперклассом».
Обратите внимание на отсутствующую деталь: не упоминается, что экземпляр подкласса должен быть в состоянии заменить экземпляр суперкласса везде в программе; не упоминается, что экземпляр подкласса должен удовлетворять всем концептуальным тестам для экземпляра своего суперкласса.

Наследование никогда не было проблемой: проблема в попытке использовать одно дерево для трёх разных концепций.

«Предпочитать структуру вместо наследования» — это по сути отказаться от наследования реализации. Мы не можем понять, как заставить его работать, так что давайте вовсе от него откажемся: сделаем совместное использование через делегирование, а не подклассы.

Eiffel и отдельные упорядоченные подходы к использованию языков вроде Java укрепляют отношение «наследование есть создание подтипов», ослабляя отношение «наследование есть повторное использование» (если один и тот же метод появляется дважды в несвязанных частях дерева, вам придётся с этим жить для сохранения свойства, что каждый подкласс является подтипом своего родителя). Это нормально, если вы не пытаетесь также смоделировать и проблемную область с помощью дерева наследования. Обычно литература по ООП рекомендует сделать это, когда речь идёт о проблемно-ориентированном проектировании.

Типажи укрепляют отношение «наследование есть специализация», ослабляя отношение «наследование есть повторное использование» (если обе суперкатегории производят одно и то же свойство экземпляра, то ни одно из них не наследуется и вы должны прописать его самостоятельно). Это нормально, если вы не пытаетесь также рассматривать подклассы как ковариантные подтипы своих суперклассов, но обычно литература по ООП рекомендует сделать это, упоминая принцип подстановки Барбары Лисков и то, что тип в сигнатуре метода означает этот тип или любой подкласс.

Я считаю, что в литературе должно быть написано следующее: «вот три типа наследования, сосредоточьтесь на одном из них». Я также считаю, что языки должны поддерживать это (очевидно, Smalltalk, Ruby и их друзья поддерживают это за счёт отсутствия каких-либо ограничений типов).

  • Если я использую наследование для совместного использования кода, не следует предполагать, что мои подклассы одновременно являются подтипами.
  • Если я использую подтипы для укрепления интерфейсных контрактов, мне должны не только позволить помечать класс в любом месте дерева как подтип другого класса в любом месте дерева, но обязать делать это. Опять же, не следует предполагать, что мои подклассы одновременно являются подтипами.
  • Если мне требуется указать концептуальную специализацию через классы, то это также не должно предполагать соблюдение дерева наследования. Мне должны не только позволить помечать класс в любом месте дерева как подмножество другого класса в любом месте дерева, но обязать делать это. Опять же, не следует предполагать, что мои подклассы одновременно являются специализациями.

Ваша модель области распределения — это не объектная модель. Это не модель абстрактного типа данных. И объектная модель — не модель абстрактного типа данных.

Вот теперь наследование опять стало простым.
Поделиться публикацией
Похожие публикации
Ой, у вас баннер убежал!

Ну. И что?
Реклама
Комментарии 43
  • +1
    А какой смысл в применении наследования для повторного использования кода?
    Для этой цели наследование реализаций в любом случае подходит плохо, так как ломает инкапсуляцию.
    • +1

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


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


      Ну вот композиция для повторного использования кода куда более прозрачна, вот только вот разработка атомарных и выделение общих компонент — долгое и нудное занятие. Куда проще нашлепать public class B:A, потом C:B, потом D:C, E:D и т.д. (видел как-то калокод с иерахией в 40 классов). Потом правда кто-нибудь влезет в В или А и все сломается, но автора уже не будет и чинить будут другие люди.

      • 0
        Для добавления свойств и поведения уже написанному коду достаточно их добавить в предка — корень иерархии. И волшебство произойдёт за счёт полиморфизма (позднего связывания кода). Например, захотели добавить свойство цвет ко всем фигурам — добавили его в их общего предка, пусть даже абстрактного, реализовали методы для работы с этим свойством; и автоматически цветность приобретут все унаследованные фигуры, даже уже написанные.
        • 0
          Звучит красиво, но на практике «добавить свойство цвет ко всем фигурам» скорее всего есть нарушение SRP, со всеми вытекающими.

          Да и вообще оказывается, что нужен не просто цвет, но и фон, а ещё тень(контур). И в каждой фигуре надо прописать что должно быть каким цветом. А ещё оказывается у окружности нет «тела», которое надо бы раскрашивать — приходится раскрашивать контур в цвет фигуры. А в нашей иерархии таких бестелесных фигур несколько, в разных ветвях — надо обобщить код вне иерархии…

          И непонятно в чем профит, если всё равно потрогали код всех фигур, а «общее» поведение в родительском классе подходит только половине наследников (хорошо, если большей — на самом деле «верхней»).
          • 0
            SRP (принцип единственной ответственности) распространяется на класс или объект, а не иерархию. Ведь в иерархии самый старый древний класс, от которого все наследуются, может тоже меняться, не влияя на реализацию потомков. Пример: в классе-предке реализуется черчение фигур на графопостроителе, а классы фигур-потомков занимаются исключительно представлением себя (векторизацией) и используют методы вывода на то инкапсулированное векторное устройство, которое подраумевает класс-предок, отдавая ему данные векторизации. Затем, с развитием технологий, появились векторные дисплеи, класс-предок переписали так, чтобы использовать выбор: отображать фигуры на графопостроителе или векторном дисплее — в зависимости от положения механического переключателя. И программу изменили только в части класса-предка, а остальную иерархию классов не трогали — все ранее написанные фигуры продолжали считать, что они отображают себя на некоем векторном устройстве вывода, возвращая лишь данные для своего построения. Затем появились растровые устройства отображения, класс-предок тоже пришлось переписать, не затрагивая код классов-потомков, таким образом, чтобы данные векторного представления фигур в нём растеризовались и отсылались на принтер или дисплей…
            • 0
              Так я не понял, какая одна ответственность у вашего предка?

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

              Отдавать данные векторизации — это не отношения «родитель-потомок», тут наследование вообще ничего не даёт.
        • 0
          Мои коллеги очень любят такое наследование. Говорият, что один extend заменяет кучу import.
          И в Play Framework его тоже слишком любят.
          Меня это очень расстраивает, потому что мешает отлаживать программу в REPL.
        • +7
          Желтизна заголовка просто зашкаливает…
          • –6
            Ага. Еще понравилось
            Часто для наследования в ООП приводят контрпример отношений между квадратом и прямоугольником
            Часто это в смысле «никогда»? Взяли неудачный пример и что? Пусть скажем ну них базовый клас Figure. Очень удобно иметь возможность обьявить некий List и пройтись по нему вызывая виртуальный метод Draw. При добавлении новой фигуры код менять не надо. Вот это круто. Вот это «частый» пример.
            • +6

              Это вполне удачный пример для понимания, где наследование применяется правильно, а где нет.
              https://en.wikipedia.org/wiki/Circle-ellipse_problem


              Об этом часто упоминают в контексте The Liskov Substitution Principle.

              • –3

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

                • +1
                  Проблема заключается в переходе от одной сущности к другой, например если мы меняем прямоугольник так, что его стороны становятся равны, то это должен быть объект типа «квадрат»

                  Такое кстати возможно в Common Lisp, где на уровне CLOS можно менять тип в рантайме
                  • +1

                    Да, вы предлагаете то, что и в статье в википедии:
                    https://en.wikipedia.org/wiki/Circle-ellipse_problem#Allow_for_a_weaker_contract_on_Ellipse


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

                    • –2
                      Почему расширенный наследованный класс не может требовать от вызывающего кода больше, чем базовый класс? Или вообще другого кода?
                      У нас есть класс животное.
                      Есть унаследованный класс класс собака.
                      У собаки есть метод «гав», который можно вызвать.
                      мояСобака = новая Собака();
                      мояСобака.Гав();
                      Я не могу вызвать метод гав у животного, потому что его (метода) нет. У животного этот метод не определен. И это разумно. Но он есть у собаки. Тем не менее, собака является частной реализацией от общего класса животное.
                      • +1
                        Почему расширенный наследованный класс не может требовать от вызывающего кода больше, чем базовый класс? Или вообще другого кода?

                        Потому что это и есть сам LSP в отображении на код :-)


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


                        Это очень сильное требование, его сложно выполнять. Многие пишут на C++/Java/C#/итп без учета этого требования, и потом у них архитектура начинает трещать по швам


                        Вам стоит изучить вот это: SOLID. И другие классические определения ООП на Википедии (начиная со статьи ООП, и дальше — полиморфизм, инкапсуляция, наследование)

                        • –1
                          Ну как бы вам сказать. Тогда наследование вообще убогое получается, если следовать LSP, и, как желтушно кричит заголовок, «бесмыссленно» (отчасти). Но следовать LSP или не следовать — дело договоренности внутри команды. Если команда следует, это одно, если не следует — это другое.
                          Команда, в которой работаю, этому принципу не следует.
                          Многие ли следуют этому принципу, что из тех, что здесь отметились? Те, кто прошлись по моим комментариям, видимо следуют (как минимум два человека), а остальные?
                          • +1

                            Обрати внимание на тех, кто там это написал — Саттер и Александреску, создатели C++ и D. У Гослинга тоже где-то было про это, сейчас не нагуглю. Короче, дизайн этих языков специально такой, чтобы форсировать LSP. Стандартная библиотека такая. Без него начинается нездоровая фигня.


                            Например, явно видно как они мучались, когда не было дженериков. Вот тебе код на C#:


                            static void update(object[] objs)
                            {
                               objs[0] = new object();
                            }
                            
                            string[] strs = new string[] { "hello", "world" };
                            update(strs);

                            При запуске он бросит исключение вроде ArrayTypeMismatchException, поскольку Шарп не может записать экземпляр объекта внутрь массива из стрингов. Это — прямое нарушение LSP. string[] — это подтип object[], но когда его попытались использовать в том же месте где object[], всё сломалось.


                            В чем проблема? Если следовать логике, то пришлось бы писать несколько одинаковых методов: static void update(string[] objs), static void update(int[] objs) — и люто копипастить. Как и делают в Golang, когда не опускаются до рефлексии и кодогенерации :)) В нормальных языках для этого есть дженерики, но когда Java и C# создавались, дженериков в них не было ещё. Поэтому массивы сделали ковариантными по типу элемента. В смысле, теперь можно отправить string[] на вход методу, который принимает object[], и это работает вот так:


                            static void Sort(object[] objs)
                            {
                               // ...
                            }
                            
                            string[] strs = new string[] { "hello", "world" };
                            Sort(strs);

                            Совершенно очевидно, что это жёсткий хак системы, сделанный от безысходности. Заказчики языка хотели уменьшить копипасту во что бы то ни стало — и это в точности тот вопрос, который обсуждает в ОП-посте.


                            ООП не предназначено для уменьшения копипасты, оно скорее запрограммировано на её увеличение. Уменьшить именно дублирование можно на уровне другого над-языка, например препроцессора шаблонов. Например, в Java такой язык обсуждается на уровне проекта Valhalla, раздел специализации дженериков.


                            Так что это всё замечательно, что вы не соблюдаете LSP, но готовьтесь к тому, что в большой системе через некоторое время таким кодом будет невозможно пользоваться :)))


                            Меня этот вопрос интересует не по причине того, что я соблюдаю или не соблюдаю LSP, а потому что это одна из основополагающих проблем, благодаря которой мир языков программирования стал таким, каким мы его знаем.

                        • 0
                          Почему расширенный наследованный класс не может требовать от вызывающего кода больше, чем базовый класс? Или вообще другого кода?

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


                          Я не могу вызвать метод гав у животного, потому что его (метода) нет. У животного этот метод не определен. И это разумно. Но он есть у собаки.

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

                      • +1
                        А что делать с методом, который меняет ширину, оставляя площадь неизменной?
                        (востребовано, к примеру, чтобы нарезать участки садоводам по 6 соток)
                        • –2
                          Этот метод вернет только одно решение, оно будет равно 0.
                          Или переопределить его так, чтобы его нельзя было вызвать (хотя вот это уже плохое решение, но все равно рабочее).
                          • +2
                            Кодировать ошибки магическими числами тоже — так себе решение.
                            • 0
                              Это не магическое число. Это как раз-таки ответ. На что можно изменить ширину, чтобы площадь осталась неизменной? На 0.
                              Увы, я не силен в математике, но попробую описать: a^2=0, где 0 это изменение площади. Отсюда a=0.
                    • 0
                      Часто это в смысле «никогда»?


                      знакомый студент недавно пытался на ООП моделировать геометрические фигуры. По началу все шло ок, но когда дошло до определения пересечений фигур, то начался треш, угар и вопли «да кто придумал этот ооп».
                  • 0
                    Все s в классе «Квадрат» являются прямоугольниками s, у которых длина равна ширине. Но в иерархии типов это отношение обратное: вы можете использовать прямоугольник везде, где используется квадрат (указав прямоугольник с одинаковой шириной и высотой), но нельзя использовать квадрат везде, где используется прямоугольник (например, вы не можете изменить длину и ширину).

                    Просто неверно выбран уровень абстракции. В таком случае есть смысл создать класс сторона и создать фабрику фигура которая будет моделировать любой многоугольник как ромб и квадрат так и октаидр и другие. Но если вы заходит чтобы ваше приложение обрабатывала все множество то может быть стоит уйти к истокам геометрии и рассмотреть класс точка и фабрику фигура которая будет моделировать все разнообразие имея в себе один из методов сторона.…
                    • +1
                      Только надо нам было просто создать прямоугольник и квадрат и нарисовать их методом .draw()
                      А теперь нам надо учебник по геометрии поддерживать.
                      • 0
                        В этом отличие студента и профессионала. Студент создаст класс прямоугольник и будет им рисовать квадрат. Профессионал все унаследует от точки и будет строить фигуры используя метод line прежде чем рисовать методом draw или render что лучше оставляя возможность рисовать различные фигуры которые захочется нарисовать в будущем. Таким образом у студента получаяется консольное приложение которое в качестве лабы идет в зачет, у профессионала получается САПР которые со временем расширяется и выростает до мощностей AutoCAD. Ничего особенного.
                        • +1
                          По-моему наоборот — профессионал с опытом разработки сначала посмотрит на задачу, и если там действительно нужно только рисовать прямоугольники и квадраты — он не будет делать САПР.
                          А вот студент — возможно.
                        • 0

                          Надо либо не обобщать либо обобщать корректно.
                          Квадрат это прямоугольник.
                          Но не "Изменяемый квадрат это изменяемый прямоугольник".
                          Квадраты и прямоугольки могут быть вообще константами.


                          В-общем, в википедии ж все написано про то, что с этим делать :)

                      • 0
                        не упоминается, что экземпляр подкласса должен быть в состоянии заменить экземпляр суперкласса везде в программе

                        Видимо, не «заменить», а «отработать без ошибок».
                        • 0
                          Все s в классе «Квадрат» являются прямоугольниками s, у которых длина равна ширине. Но в иерархии типов это отношение обратное: вы можете использовать прямоугольник везде, где используется квадрат (указав прямоугольник с одинаковой шириной и высотой), но нельзя использовать квадрат везде, где используется прямоугольник (например, вы не можете изменить длину и ширину).

                          А я вот вижу это абсолютно иначе — вы можете использовать квадрат везде, где используется прямоугольник (любой метод, который может работать с прямоугольником или возвращает прямоугольник, будет корректно работать и с квадратом), но нельзя использовать прямоугольник везде, где используется квадрат (по тем же причинам — методы работающие с квадратами не смогут корректно обработать произвольный прямоугольник).
                          • 0
                            Еще наследование иногда используется для эмуляции типов-сумм (variants, tagged unions, discriminated unions). В таком случае родитель выступает в роле маркера. Он, как-бы, говорит — мои наследники — это разные представлени одного класса, и каждый, кто хочет с ним работать, должен уметь работать с каждым из моих наследников.
                            • 0
                              Начнём с разновидностей наследования.
                              Онтологическое наследование указывает на специализацию: вот эта штука — специфическая разновидность той штуки (футбольный мяч — это сфера и у неё такой-то радиус).
                              Допустим. Но разновидность ли это?
                              Наследование абстрактного типа данных указывает на замещение: у этой штуки такие же свойства, как у той штуки, и такое-то поведение (это принцип подстановки Барбары Лисков).
                              Есть абстрактный тип строк. Он абстрактен потому, что не определён внутренний формат строки и не известно, как реализовать операции над этими строками. Но при этом весь интерфейс строки он определяет. Есть ещё конкретный тип трёхмерного декартового вектора, компоненты которого — именованные члены типа double. На каком таком основании конкретный вектор будет что то наследовать от абстрактной строки? Есть ещё конкретный тип строки анси-символов, определяющий строку, начинающуюся с восьми байт своей длины, завершаемую не учитываемым в длине символом '\0', между которыми в порядке символов располагаются без промежутков однобайтные коды символов согласно таблице, экземпляр имеет приватный указатель сразу за длину. Если уж есть абстрактный тип сток, то он неизбежно будет предком для конкретного класса строк. Так отдельная ли это разновидность наследования? В принципе да, но не от «онтологического наследования».
                              Наследование реализации связано с совместным использованием кода: эта штука принимает некоторые свойства той штуки и переопределяет или дополняет их таким-то образом. Наследование в моей статье «О наследовании» именно такого и только такого типа.
                              Есть два нечта, одно наследует реализацию другого. Отдельная ли это разновидность наследования? Да, потому что абстрактный тип может определять внутренние форматы части данных, тогда в нём будет реализована часть операций над экземплярами, тогда любой наследующий его интерфейс может наследовать эту реализацию, но может быть и так, что конкретный тип наследует реализацию другого конкретного типа, а не абстрактного. Есть два нечта, ни одно не является разновидностью другого, но одно наследует реализацию другого. Потом решено изменить реализацию. Счастливой отладки. Так отдельная ли это разновидность наследования? Так можно ли отделять данную разновидность от «онтологического наследования»? Нет. Оделить можно, отделять нельзя, так как одно — условие другого.
                              Итак у нас есть две разновидности наследования: наследование только интерфейса и наследование интерфейса и реализации. Можно ли реализовать их одним инструментом? И да и нет. Потому что реализовать их можно только двумя разновидностями одного инструмента, но так как это две разные разновидности не друг друга, а третьего более общего, то каждая из них — отдельный инструмент. Противоречит ли разновидность общему? Нет, иначе это не разновидность. Предположим, мы отбросим наследование. Но тогда мы полностью лишимся классификации чего либо, так как любая классификация зиждется на определении того, разновидностью каких категорий является каждый объект и каждая конкретная (в ООП смысле) категория. И смысл теряет всё.
                              • 0

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


                                Если есть обобщение то есть и наследование (кошка наследует все свойства общие для всех живтоных). Обратное — неверно (прототипное наследование не является обобщением, например "Типичный житель Москвы обладает карими глазами. Вася — совсем как типичный житель Москвы, только глаза голубые")

                                • 0
                                  Если нет обобщения, то нельзя наследовать. Кошка наследует свойства животных, но не машин.
                                  «Типичный житель Москвы обладает карими глазами. Вася — совсем как типичный житель Москвы, только глаза голубые»)
                                  Значит Вася не типичен для Москвы. А обратное как раз не верно. Кошка наследует свойства, общие для всех животных, но не наследует свойств, специфичных для жука, несмотря на то, что жук относится к одному с кошкой классу (в ООП смысле).
                                  • 0

                                    Это вы про наследование при обобщении. Просто оно популярнее, потому, что привычнее и вам кажется что другого не бывает. Наследование при прототипировании это "Мне нужен халат как такой, но с перламутровыми пуговицами". То есть не "Все жители Москвы такие" а "Жители Москвы такие, если я не скажу обратного".

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

                                        Это и есть наследование в определении UML notation guide кажется.
                                        И такое наследование есть javascript и self


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

                                        Это не значит что это не наследование, это значит, что таоке наследование более непредсказуемо чем наследование при обобщении.


                                        Если сумасшедший генетик вернётся в прошлое и сделает так, чтоб млекопитающие кормили детёнышей смесью только воды, солей и белка

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


                                        Может быть несколько разных классификаций одного и того же в зависимости от целей.

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

                                  Допустим. Но разновидность ли это?
                                  Наследование абстрактного типа данных указывает на замещение: у этой штуки такие же свойства, как у той штуки, и такое-то поведение (это принцип подстановки Барбары Лисков).

                                  Есть абстрактный тип строк. Он абстрактен потому, что не определён внутренний формат строки и не известно, как реализовать операции над этими строками. Но при этом весь интерфейс строки он определяет. Есть ещё конкретный тип трёхмерного декартового вектора, компоненты которого — именованные члены типа double. На каком таком основании конкретный вектор будет что то наследовать от абстрактной строки? Есть ещё конкретный тип строки анси-символов, определяющий строку, начинающуюся с восьми байт своей длины, завершаемую не учитываемым в длине символом '\0', между которыми в порядке символов располагаются без промежутков однобайтные коды символов согласно таблице, экземпляр имеет приватный указатель сразу за длину. Если уж есть абстрактный тип сток, то он неизбежно будет предком для конкретного класса строк. Так отдельная ли это разновидность наследования? В принципе да, но не от «онтологического наследования».
                                  Наследование реализации связано с совместным использованием кода: эта штука принимает некоторые свойства той штуки и переопределяет или дополняет их таким-то образом. Наследование в моей статье «О наследовании» именно такого и только такого типа.

                                  Есть два нечта, одно наследует реализацию другого. Отдельная ли это разновидность наследования? Да, потому что абстрактный тип может определять внутренние форматы части данных, тогда в нём будет реализована часть операций над экземплярами, тогда любой наследующий его интерфейс тип может наследовать эту реализацию, но может быть и так, что конкретный тип наследует реализацию другого конкретного типа, а не абстрактного. Есть два нечта, ни одно не является разновидностью другого, но одно наследует реализацию другого. Потом решено изменить реализацию. Счастливой отладки. Так отдельная ли это разновидность наследования? Так можно ли отделять данную разновидность от «онтологического наследования»? Нет. Отделить можно, отделять нельзя, так как одно — условие другого.
                                  Итак у нас есть две разновидности наследования: наследование только интерфейса и наследование интерфейса и реализации и «онотологическое наследование» в качестве их обоснования. Можно ли реализовать их одним инструментом? И да и нет. Потому что реализовать их можно только двумя разновидностями одного инструмента, но так как это две разные разновидности не друг друга, а третьего более общего, то каждая из них — отдельный инструмент. Противоречит ли разновидность общему? Нет, иначе это не разновидность. Предположим, мы отбросим наследование. Но тогда мы полностью лишимся классификации чего либо, так как любая классификация зиждется на определении того, разновидностью каких категорий является каждый объект и каждая конкретная (в ООП смысле) категория. И смысл теряет всё.
                                  • 0
                                    В ООП много косяков. Лично меня постоянно сбивает с толку время жизни програмного объекта и реального объекта.
                                    Если уже есть реальный файл на диске, от зачем писать
                                    File f = new File();? Эта конструкция тянет на создание файла, но на самом деле нет.

                                    Если уже заведен клиент компании в базе данных, то зачем писать
                                    Client cl = new Client();? Эта конструкция тоже претендует на создание чего-то нового, но клиент-то уже есть.
                                    • 0

                                      Воспользуйтесь ORM или объектной базой данных. Это проблема конкретных API а не ООП

                                      • 0
                                        Чем больше прослоек, тем больше глюков. Я когда исходники смотрю, то все время Черномырдина цитирую. «Вино мы пьем чтобы было здоровье, а здоровье нужно чтобы пить водку». Или подругому «Фреймвокри мы подключаем для удобства и бытсроты, а удобство и быстрота позволяет кплеать много ошибок»
                                        • 0

                                          Обектная база данных. Никаких прослоек :)

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

                                    Самое читаемое