Наследование реализаций: закопайте стюардессу

    Ключевое противоречие ООП


    Как известно, классическое ООП покоится на трех китах:


    1. Инкапсуляция
    2. Наследование
    3. Полиморфизм

    Классическая же реализация по умолчанию:


    1. Инкапсуляция — публичные и приватные члены класса
    2. Наследование — реализация функционала за счет расширения одного класса-предка, защищенные члены класса.
    3. Полиморфизм — виртуальные методы класса-предка.

    Но еще в 1986 году была обозначена серьезнейшая проблема, кратко формулируемая так:


    Наследование ломает инкапсуляцию


    1. Классу-потомку доступны защищенные члены класса-предка. Всем остальным доступен только публичный интерфейс класса. Предельный случай взлома — антипаттерн Паблик Морозов;
    2. Реально изменить поведение предка можно только с помощью перекрытия виртуальных методов;
    3. Принцип подстановки Лисков обязывает класс-потомок удовлетворять всем требованиям к классу-предку;
    4. Для выполнения пункта 2 в точном соответствии с пунктом 3 классу-потомку необходима полная информация о времени вызова и реализации перекрытого виртуального метода;
    5. Информация из пункта 4 зависит от реализации класса-предка, включая приватные члены и их код.

    В теории мы уже имеем былинный отказ, но как насчет практики?


    1. Зависимость, создаваемая наследованием, чрезвычайно сильна;
    2. Наследники гиперчувствительны к любым изменениям предка;
    3. Наследование от чужого кода добавляет адскую боль при сопровождении: разработчики библиотеки рискуют получить обструкцию из-за поломанной обратной совместимости при малейшем изменении базового класса, а прикладники — регрессию при любом обновлении используемых библиотек.

    Все, кто используют фреймворки, требующие наследования от своих классов (WinForms, WPF, WebForms, ASP.NET), легко найдут подтверждения всем трем пунктам в своем опыте.
    Неужели все так плохо?


    Теоретическое решение


    Влияние проблемы можно ослабить принятием некоторых конвенций:


    1. Защищенные члены не нужны
    Это соглашение ликвидирует пабликов морозовых как класс.


    2. Виртуальные методы предка ничего не делают
    Это соглашение позволяет сочетать знание о реализации предка с независимостью от нее реализации уже в потомке.


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


    4. Экземпляры предка никогда не создаются
    Это соглашение позволяет избавиться от несоответствия требований к виртуальными методам (публичный контракт класса) с одной стороны и обязанностью ничего не делать (защищенный контракт класса) с другой. Теперь принцип подстановки Лисков можно соблюсти, не вступая в порочную связь с закрытым содержимым предка.


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


    Результат: если класс-предок состоит из публичных виртуальных пустых методов и требований к ним для потомков, то наследование уже не ломает инкапсуляцию. Что и требовалось доказать.


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


    Практические решения


    1. Виртуальные методы-пустышки уже есть во многих языках и носят гордое звание абстрактных.
    2. Классы, экземпляры которых создавать нельзя, тоже есть во многих языках и даже имеют то же звание.
    3. Полное соблюдение указанных соглашений в языке C++ использовалось как паттерн для проектирования и реализации Component Object Model.
    4. Ну и самое приятное: в C# и многих других языках соглашения реализованы как первоклассный элемент "интерфейс".
      Происхождение названия очевидно — в результате соблюдения соглашений от класса остается только его публичный интерфейс. И если множественное наследование от обычных классов — редкость, то от интерфейсов оно доступно без всяких ограничений.

    Итоги


    1. Языки, где нет наследования от классов, но есть — от интерфейсов (например, Go), нельзя лишать звания объектно-ориентированных. Более того, такая реализация ООП правильнее теоретически и безопаснее практически.
    2. Наследование от обычных классов (имеющих реализацию) — чрезвычайно специфический и крайне опасный архаизм.
    3. Избегайте наследования реализаций без крайней необходимости.
    4. Используйте модификатор sealed (для .NET) или его аналог для всех классов, кроме специально спроектированных для наследования реализации.
    5. Избегайте публичных незапечатанных классов: пока наследование не выходит за рамки своих сборок, из него еще можно извлечь пользу и ограничить вред.

    PS: Дополнения и критика традиционно приветствуются.

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

    Ну. И что?
    Реклама
    Комментарии 349
    • +1
      Теперь понятно почему по умолчанию все классы в Kotlin sealed а методы final.
      • 0

        А это общее правило параноика для любого ЯП (хотя больше всего его любят в C++): если вы до конца не определились как вам оформить класс, то делайте его приватным, делайте его конструктор приватным, делайте все методы приватными и невиртуальными, и запрещайте наследование. Чтобы изменить любой из этих пунктов нужна вполне конкретная причина.

        • 0
          В теории это хорошо, но писать sealed/final быстро надоедает, даже с автокомплитом. Та же ситуация что и с переменными и типами, поведение по умолчанию (mutable, nullable) слишком часто надо переопределять.
          • +1

            Не надоедает, если понимать, зачем это делается. Конечно, нехорошо, что лучшая практика штрафуется, но этот штраф довольно легкий и требует всего лишь выработки одной хорошей привычки.

            • +1
              Или пары шаблонов в студии.
              • +1
                В данном случае в IDEA, хоте мне больше нравится мысль взять новый «хороший» ЯП и пользоваться всеми плюшками из коробки :) Конечно не всегда есть такая возможность.
                • 0
                  Я с IDEA не работал, но разве она не поддерживает написание своих сниппетов?
          • НЛО прилетело и опубликовало эту надпись здесь
            • +2

              Я же написал в явном виде — "нужна причина" :-) У вас причина есть, соответственно вы осознанно будете делать классы/методы/конструкторы паблик. Но не у всех есть такая причина.

            • +4

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

              • +3

                Я ни в коем случае не говорил, что нужно делать приватные классы по умолчанию. Перечитайте пожалуйста: "если вы до конца не определились как вам оформить класс, то делайте его приватным...". Если вы хотите сделать класс частью интерфейса вашей библиотеки, это просто означает что вы определились.

              • +6
                Меня всегда удивляло стремление отдельных людей выдумывать «правила», а потом героически их преодолевать!
                Инкапсуляция на 100% — это утопия, так не бывает в жизни. ООП — лишь средство более элегантно решить некоторые (но далеко не все) проблемы, но не нужно его идеализировать и обожествлять! Если предоставить «клиенту» посмотреть на «внутренности» — это не плохо, просто «клиент» (пользователь библиотеки), принимая решения, должен осознавать последствия сделанного им выбора. Я уверен, что разумный доступ к внутренностям объекта (и одновременным пониманием, чем это чревато) позволяет писать более эффективный код, и делать это быстрее, а не тратить время на героическое преодоление правил. Правила должны помогать осуществлять задумки, а не мешать им. А вот ответственность за сильное связывание, спагетти-код и т.д. лежит не на языке, и даже не на библиотеке, а на конкретном разработчике ее использующем.
                И если библиотека хорошо выполняет свою задачу, пусть она трижды вся public, я буду ее использовать, и скажу ее разработчику лишь спасибо!
                • +1
                  Меня всегда удивляло стремление отдельных людей выдумывать «правила», а потом героически их преодолевать!

                  Я не пропагандирую это "правило параноика" и сам ему не следую. Не это конкретное правило, но правила вообще имеют смысл когда уровень квалификации "среднего" участника команды приводит к регулярным наступаниям на грабли. С точки зрения поставки фич бизнесу всегда проще сказать "всегда делай вот так", чем ждать пока коллега соберёт все грабли, пропустит все дедлайны, научится и станет делать "правильно" осознанно.


                  И если библиотека хорошо выполняет свою задачу, пусть она трижды вся public, я буду ее использовать, и скажу ее разработчику лишь спасибо!

                  Ну не всё так просто:


                  • У разработчика при таком подходе будут связаны руки: т.к. всё public и всё часть контракта библиотеки, нельзя будет просто так пойти и отрефакторить что-то. Каждый новый класс — это minor релиз, каждое переименование любого класса — это major релиз. Patch релизы будут предполагать только изменения кода реализации существующих классов и методов.
                  • Вы в итоге разработчику спасибо не скажете, т.к. на ранних стадиях развития библиотеки каждый второй релиз скорее всего будет major — с изменённым контрактом.
                  • +1

                    вот так копаешься в либах от Microsoft: вроде всё понятно, так и так, ща вот тут подлезу и будет шоколадно вообще… итут НННА ТЕБЕ INTERNAL.

                    • 0
                      Я тоже копался в некоторых либах от MS. И знаете, internal там далеко не самое страшное зло.
              • +3
                Наследники гиперчувствительны к любым изменениям предка.

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

                Бывают случаи, когда базовый класс сам по себе самостоятелен и реализует заложенный функционал. Но множество частных случаев требуют переопределения всего лишь одного-двух методов. Внимание, вопрос: с точки зрения автора статьи, как обходиться в таких ситуациях?
                • +2
                  Бывают случаи, когда базовый класс сам по себе самостоятелен и реализует заложенный функционал. Но множество частных случаев требуют переопределения всего лишь одного-двух методов. Внимание, вопрос: с точки зрения автора статьи, как обходиться в таких ситуациях?

                  Не автор, но прокомментирую :-)


                  Часто забывают, что ООП — это от слова "объект", а не от слова "класс". Задача программиста — сделать так, чтобы получались объекты, обладающие требуемым поведением. В среднестатистическом мейнстримном языке типа C#/Java есть 2 подхода:


                  1. Если мне нужно новое поведение, я наследую существующий класс и переопределяю какой-то аспект его поведения.
                  2. Если мне нужно новое поведение, я немного иначе "строю" объект, поведение которого мне нужно изменить.

                  Если весь код написан таким образом, что единственный способ сконструировать объект это new ЧтоТоТам(), конечно тут кроме наследования ЧтоТоТам нет вариантов. Но можно же изначально заложиться на new ЧтоТоТам(new КакЯДелаюВотЭто(), new ИЕщёКакЯДелаюВотЭто()). В таком случае получается на порядок больше мелких классов, где одни классы описывают какой-то конкретный аспект поведения, а другие — просто скручивают несколько таких аспектов поведения в один "настроенный объект". Статья автора, если я правильно понял, про такой подход.

                  • –6
                    1) Это называется АОП — Аспектно-ориентированное программирование. И оно — не панацея, так как не позволяет безболезненно связать между собой разные аспекты.
                    2) Это верно только для простых алгоритмов, которые будут вызываться изнутри кода. Вроде SortedVector(SortingAlgorithm); SortedVector(new Bubble()); SortedVector(new QSort());
                    Если это более сложный класс, который, тем более, должен иметь доступ к приватным полям объекта, ваш подход ещё хуже, так как требует объявить ваши КакЯДелаюВотЭто и ИЕщёКакЯДелаюВотЭто друзьями класса ЧтоТоТам. Далеко не все ЯП позволяют потомкам «друзей» оставаться «друзьями».
                    • +7
                      Это называется АОП

                      Это ни в коем случае не АОП. Это самое обычное ООП + делегирование.


                      Это верно только для простых алгоритмов… Если это более сложный класс, который, тем более, должен иметь доступ к приватным полям объекта, ваш подход ещё хуже

                      Посмотрите примеры использования паттерна "Стратегия" — это собственно делегирование в чистом виде и есть.

                      • –10
                        Это ни в коем случае не АОП

                        Думаете? Подумайте лучше.
                        • 0

                          Посмотрите пожалуйста внимательнее на вашу ссылку, ключевое слово — сквозная функциональность:


                          Для программы, написанной в парадигме ООП, любая функциональность, по которой не была проведена декомпозиция, является сквозной

                          В случае моего примера выше мы в явном виде провели декомпозицию.

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

                            Как я уже писал выше, это работает для простых алгоритмов. Как только у вас появляется сложное поведение, которое не умещается в простую стратегию, все ваши красивые приёмы с декомпозицией перерастают в монстров.
                            Либо это будет чудище Франкенштейна, сшитое из десятков кусков, каждый метод которых принимает по дюжине разных структур, и тогда собирание монстра превратится в настоящее испытание и станет источником багов, а интерфейсы самого чудища в поисках упрощения обрастут сотнями ненужных обывателям внутренних методов.
                            Либо это будут сильно связанные классы, знающие о кишках друг друга и, в итоге, приводящих к тем же проблемам, что и простое наследование, но не дающее его преимуществ.
                            Либо это будет очередное АОП.
                            Вы не можете избавиться от сложности, её можно только выдавливать из одного места в другое. Декомпозиция выглядит достаточно заманчиво и очень часто именно она должна использоваться вместо наследования. Но не всегда.
                        • 0
                          Тогда считайте, что каждый виртуальный метод — это такая запись делегирования, не загромождающая код. Ведь функция — это тоже объект.
                          • +3

                            А мне почему-то кажется что это реализация IoC через DI, в данном случае — constructor injection. А еще мне кажется, что из всего SOLID, Liskov Substitution самый жесткий и самый неоднозначный. Если вдуматься, последовательное его использование эффективно кастрирует саму идею наследования. О чем, впрочем, и написана эта статья.

                            • 0
                              Так ведь без соблюдения LSP получается, что мы вынуждены учитывать возможные реализации класса и как-то подстраиваться под них особым образом по месту их использования, что убивает идею полиморфизма подтипов. Похоже, куда не посмотри нет в жизни счастья.
                              • 0

                                Зачем подстраиваться? Изначально же понятно, что полиморфизм обеспечивается только в рамках спецификации класса-предка.

                                • 0

                                  Затем, что именно принцип Лисков позволяет обеспечить полиморфизм "в рамках спецификации класса-предка".

                                  • 0

                                    Да, но наследование ведь изначально предполагает, что потомок реализует спецификацию предка? Реализация меняется, спецификакия — нет.

                                    • 0
                                      Да, но наследование ведь изначально предполагает, что потомок реализует спецификацию предка?

                                      Это и есть принцип Лисков, соблюсти который при наследовании реализации нетривиально.

                                      • 0

                                        нетривиально != невозможно. И даже сильнее: нетривиально != сложно. Нетривиально, означает, что потребуются не только руки + клавиатура, но еще и мозг. Но мы ведь не боимся пускать в ход столь мощный инструмент?
                                        Кстати, вас не сильно затруднит дополнить вот это и это вашим вариантом реализации через инкапсуляцию? Мне кажется, это было бы полезной иллюстрацией.

                                        • 0

                                          через инкапсуляцию композицию, конечно же. прошу прощения.

                                          • 0
                                            Я не автор статьи и не эксперт, но это могло бы выглядеть примерно так
                                            • 0
                                              > Нетривиально, означает, что потребуются не только руки + клавиатура, но еще и мозг.

                                              Нет. Есть принципиальная проблема, что «спецификация предка» — это не интерефейс, и формально нигде не описана. То есть меняя поведение в наследнике вы реализуете «спецификацию», которая есть только у вас в голове. А формально, скорее всего (если вы не наследуете абстрактный класс — точно), нарушаете LSP.
                                  • 0

                                    Почему вы наследование интерфейсов называете кастрацией?

                                    • 0

                                      потому, что наследуется только интерфейс. Как китайский "iPhone". Выглядит так же, а что внутри неизвестно. Для повтороного использования кода приходится применять другие механизмы (трейты, композицию+декоратор). Нет, я не против всего этого. Более того, прежде чем наследовать свои классы от неподконтрольного мне кода я еще очень подумаю. Но почему я должен отказываться от наследования в собственном коде?

                                      • 0
                                        Как китайский "iPhone". Выглядит так же, а что внутри неизвестно.

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


                                        Но почему я должен отказываться от наследования в собственном коде?

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

                                        • 0
                                          Неявное предположение "меньше кода — проще сопровождение" в данном случае неверно.

                                          Дело не собственно в количестве кода. Дело в том, что одинаковая функциональность оказывается реализована во множестве мест. Если требуется ее изменить, менять придется также во многих местах. При этом не очевидно, что в разных местах она будет реализована одинаково. Это все тоже создает проблемы при сопровождении. Решения есть, но они уводят нас очень далеко от ООП.


                                          Минимальной единицей контроля аффекта от изменений становится не один класс, а вся иерархия.

                                          Совершенно верно. В этом и суть областей видимости. Цена известна заранее. Платить ее или нет нужно решать в каждом конкретном случае. Но это не отменяет необходимости поиска консенсуса между теми, кто эту цену в конечном результате будет платить.


                                          Ну и инкапсуляцию тоже нельзя доводить до абсолюта. Скажем полезно будет указать, что вычислительная сложность метода justSolveTheProblemHereAndNow(int n) составляет O(n!), а findTheBestWifeFor(Person p) прокачает через сервер содержимое всех сайтов знакомств в интернете.

                                          • 0
                                            Дело в том, что одинаковая функциональность оказывается реализована во множестве мест.

                                            Не надо путать композицию с копипастой — одинаковая функциональность реализуется ровно один раз.


                                            Скажем полезно будет указать, что вычислительная сложность метода justSolveTheProblemHereAndNow(int n) составляет O(n!)

                                            Это не нарушение инкапсуляции, а уточнение публичного контракта.

                                            • 0
                                              Не надо путать композицию с копипастой — одинаковая функциональность реализуется ровно один раз.

                                              Хорошо, не будем.


                                              Это не нарушение инкапсуляции, а уточнение публичного контракта.

                                              Абсолютно согласен. Так же как и "вызывает add()".

                                              • 0

                                                "Вызывает add" в исходном публичном контракте нет.

                                                • 0

                                                  Значит информации этого контракта недостаточно для реализации расширения через наследование.

                                • 0
                                  > Если это более сложный класс, который, тем более, должен иметь доступ к приватным полям объекта

                                  Нонсенс. Если внешний код должен иметь доступ к полю — это не приватное поле.

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

                                    Простой пример: UndoStack. Если нам понадобится расширить его функционал возможностью коммитов на внешнюю машину (с вероятными былинными отказами), то без доступа к буферу команд ничего не получится. Никто не оставляет публичными команды манипуляций с буферами и не оставляет их для стратегий. Значит, единственная возможность — лезть в потроха. С наследованием всё просто. А без него?
                                    • 0
                                      Наследование расширяет функционал предка(вообще тезис… спорный, но пусть), враппер расширяет функционал. А к чему это?

                                      Если вам надо UndoStack, а он sealed, или буфер не protected, и исходников нет — то жопа.
                                      Если исходники есть — вытащите стратегию, это типа в 3 клика делается. Код с вашим наследником совпадёт чуть менее, чем полностью.
                                      • 0
                                        Напомню,
                                        > 3) Используйте модификатор sealed (для .NET) или его аналог для всех классов, кроме специально спроектированных для наследования реализации.
                                        > 4) Избегайте публичных незапечатанных классов: пока наследование не выходит за рамки своих сборок, из него еще можно извлечь пользу и ограничить вред.

                                        Такие классы, обычно, поставляются фреймворками. Очень часто фреймворк в бинариках поставляется, и далеко не факт, что его можно будет пересобрать без лишней боли. Очень часто любое изменение исходных кодов фреймворка (равно как и дублирование кода из фреймворка с последующей правкой под себя) требует изменения лицензии. Очень часто архитекторы даже не задумываются, что такая функциональность в принципе может понадобиться, или оставляют это на откуп конечным разработчикам, которые ставят sealed/final на автомате Вот и думайте, что делать, когда пойдёт волна свежих программистов, которые будут поголовно запечатывать свои классы потому, что Bonart так написал.
                                        Так же, напомню, что враппер не обладает никакими из свойств оборачиваемого объекта, даже если их унаследовать от одного интерфейса, связи «is a» точно не получится, что, конечно, не важно, когда у тебя понятия «класс» нет и не предвидится или когда архитектор предусмотрел это и потребовал интерфейс, но так бывает далеко не всегда.

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

                                          Вообще, это достаточно здравая идея. Класс, от которого можно эффективно наследовать должен быт соответствующим образом подготовлен. При его разработке должны быть убраны все возможные побочные эффекты, затрудняющие наследование. Если избавиться от них невозможно, их описание должно быть включено в спецификацию. Такая спецификация должна быть обязательно подготовлена, примерно так. Хорошо, если также будет открыт код. Если этого не сделано наследование от такого класса небезопасно.
                                          B вообще, в некоторых недавно появившихся языках, наследование запрещено по-умолчанию. Чтобы разрешить надо указывать open.

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

                                            Наследуем реализации — скрываем слишком много, наследуем интерфейсы — не накладываем ограничений, наследуем контракты — не проверяем корректность реализации, проверяем корректность реализации — наследуем реализации…
                                            • 0
                                              А что может быть небезопасного в наследовании неподготовленного класса без виртуальных методов, например?
                                              • 0

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

                                                • 0
                                                  Так и что здравого в пометке всего sealed?

                                                  Наличие виртуального метода подразумевает подготовку к наследованию. Если вдруг(!) у публичного класса подготовка ненадлежащая — sealed, в качестве исключения.
                                                  • 0

                                                    У публичного класса в 95 % случаев подготовка отсутствует, в 4,9% — ненадлежащая.

                                                    • 0
                                                      У публичного класса в 99% нет виртуальных методов. В 0.9% от него никому в голову не придёт наследоваться.

                                                      Пометка всего sealed сродни установке лежащих полицейских среди поля — машины там не гоняют, зато люди спотыкаются.
                                                      • 0
                                                        У публичного класса в 99% нет виртуальных методов.

                                                        У класса, спроектированного для наследования, виртуальные методы есть всегда.
                                                        От класса без виртуальных методов наследоваться бесполезно.

                                                        • 0
                                                          > От класса без виртуальных методов наследоваться бесполезно.

                                                          Ну, это вы загнули. Часто вы в C# перекрываете виртуальные члены Object? ) Если бы ToString() и GetHashCode() были в интерфейсе — вообще не перекрывали бы.
                                                          • 0
                                                            Часто вы в C# перекрываете виртуальные члены Object?

                                                            Да, хочу чтобы сравнение и ключи в dictionary работали нормально.
                                                            Еще хочу чтобы в отладчике и логах информация была читаемой.
                                                            И это ничего, что наследование от object слегка недобровольное?

                                                            • 0
                                                              А сравнение и dictionary и так, в целом, работают. ToString легко заменить не виртуальной пропертью(или даже полем).

                                                              > И это ничего, что наследование от object слегка недобровольное?

                                                              Как так? Вы же хотите, чтобы в отладчике и логах информация была читаемой?

                                                              Ну ладно, пусть не Object.
                                                              Берём официальное руководство по C#, раздел «наследование» (https://msdn.microsoft.com/ru-ru/library/ms173149.aspx) и смотрим на пример:

                                                              // ChangeRequest derives from WorkItem and adds a property (originalItemID)
                                                              // and two constructors.
                                                              public class ChangeRequest: WorkItem
                                                      • +1
                                                        А у интерфейсов они 100% хорошие, симпотичные, да в туфельках отличных, ага.
                                                        • 0
                                                          А что не так с интерфейсами?
                                                          • 0
                                                            А что не так с публичными классами?
                                                            • 0
                                                              Отличная от нуля вероятность кривой имплементации.
                                                              • 0
                                                                Отличная от нуля вероятность кривой имплементации. И не подумайте, что я вас передразниваю.
                                                                • 0
                                                                  В интерфейсе, по определению, нет имплементации. Я думаю, вы обезьянничаете.
                                                                  • 0
                                                                    «Имплементация» интерфейса — фича весьма нехарактерная для dotNet.
                                                        • 0

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

                                                          • 0
                                                            sealed предлагается использовать на уровне класса.

                                                            sealed на уровне метода подразумевает, что родитель был сначала virtual, потом был наследник, а потом решили, что эта иерархия плохо подготовлена. На мой взгляд, это «мы тут плохо надизайнили, но надо паблишить». Для либы — хак, для своего кода вообще непонятно кому надо.
                                                            • +1

                                                              Это чертовски здорово, когда весь дизайн в одних добрых руках. Но может быть такой вариант. Беру я некую библиотку классов, которая написана замечательными специалистами и предполагает, что от их классов будет наследование. Они вылизали свои методы до блеска, исключили все побочные эффекты, снабдили библитотеку подробнейшей документацией, да еще на ютуб выложили 1000 часов лекций по ее использованию.
                                                              И вот пишу я какую-то жутко полезную утилиту, в которой свои классы наследую от классов из супербиблиотеки. Но у меня же нет столько времени и денег, чтобы снимать профессиональные ролики для ютуба? И вылизывать мои методы, которые перекрывают библиотечные, чтобы избежать проблем при наследовании мне тоже особо некогда. И вообще я ее для себя писал, но я же не жадина, пусть люди пользуются! Вот только обещать осутствие проблем при наследовании я не могу. Поэтому я и поставлю sealed на этих моих методах, чтобы никого не вводить "во искушение". Хотите инкорпорировать? Инкорпорируйте! Хотите наследовать? Наследуйте! Хотите перкрывать вот этот и этот метод? Нет, лучше не надо. Вот будет у меня отпуск, я вот этот метод отполирую до блеска, и в следующей версии sealed уберу. То-то вам будет радости :-)

                                                              • 0
                                                                > Поэтому я и поставлю sealed на этих моих методах,

                                                                вы запечатаете не свой метод, а библиотечный, и «Хотите наследовать? Наследуйте!» станет издевательством.

                                                                В остальном — правильно: sealed позволяет публиковать недоделанную фигню, имитируя заботу о том, кто с матами будет пытаться обойти sealed.
                                                                • 0

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


                                                                  Что значит "публиковать недоделанную фигню"? Если я, веду разработку на одном из публичных ресурсов типа github или bitbucket "фигня" будет опубликована начиная с первого коммита.

                                                                  • 0
                                                                    У вас одни противоречащие параграфы. Зачем мне ваша жутко полезная утилитка, если при любом чихе мне надо наследоваться от базового, теряя ваш функционал? Почему вы при этом говорите «я же не жадина, пусть люди пользуются»?

                                                                    И, главное, зачем именно нужен sealed, если я могу матюгнуться, форкнуть и его удалить. Опубликовав исходники «не вводить во искушение» уже не в вашей власти.
                                                                    • 0
                                                                      Зачем мне ваша жутко полезная утилитка, если при любом чихе мне надо наследоваться от базового, теряя ваш функционал?

                                                                      инкорпорируйте и не теряйте. Дальше, от своих классов, можете наследовать.


                                                                      Почему вы при этом говорите «я же не жадина, пусть люди пользуются»?

                                                                      Потому, что закрывая наследование реализации предоставляю другой, более безопасный [в данном конкретном случае] [с моей точки зрения] метод.


                                                                      Опубликовав исходники «не вводить во искушение» уже не в вашей власти.

                                                                      Не в моей власти запретить другим "вводить во искушение". Но это уже не мои проблемы. Я закрыл опасное место, автор форка открыл. Остальные решат, что им больше подходит.

                                                                      • 0
                                                                        > инкорпорируйте и не теряйте.

                                                                        Да проще на лету подменять код и ставить везде паблик. Вопрос пары лишних строк в билдфайле.
                                                                        • 0
                                                                          > Но это уже не мои проблемы.
                                                                          > Я закрыл опасное место

                                                                          Вы или крестик снимите, или одно из двух.
                                                                          • 0

                                                                            Да почему же? Я что, должен страдать о того, что кто-то форкнул мой код, потом покорежил его, а потом? Строго говоря это уже вообще не мой код. Бред какой-то...

                                                                            • 0
                                                                              Вы вообще не должны страдать, даже sealed можно не ставить
                                                          • 0

                                                            Вот только пользы от такого наследования около нуля — без перекрытия методов полиморфизма не будет.

                                                            • 0
                                                              Если бы наследование было только ради полиморфизма, то добавлять новые (публичные) свойства и методы давно запретили.
                                                      • 0
                                                        Враппер, реализующий интерфейс, обладает всеми нужными свойствами. Я бы не рекомендовал использовать фреймворк, использующий «is a» по классу, реализующему интерфейс — его проектировали люди, в принципе не понимающие что они делают. Наличие исходников без права их использования — тоже плохой знак.

                                                        Вообще, как только в голове появилось «архитекторы даже не задумываются», то наследоваться от такого — не самая светлая мысль.

                                                        Впрочем, ставить sealed на автомате — тоже не очень хорошая идея. Надо запрещать случайно прострелить себе ногу, а не осмысленно.
                                                        • 0
                                                          То есть мы выбрасываем Java, выбрасываем .Net, у них же реализация нефинальных классов сокрыта, выбрасываем Qt, исходники нельзя переиспользовать без покупки, Wx выбрасываем, к чёрту Unity и Unreal Engine. Это, естественно, не полный список, но даже его хватает для того, чтобы понять — в помойку всё.

                                                          Вы немного не понимаете, в чём дело. Порой нужно расширить функциональность для тех классов, в которых изначально такое расширение не предусматривалось не по злому умыслу, а по незнанию. Пример с UndoStack я уже приводил. Если UndoStack вне области, доступной для модификации (как минимум, по банальной причине бинарной совместимости), ваш подход мне ничего не даст. Ничего не дадут мне ни интерфейсы, ни подходы в стиле Rust, ни врапперы. Я либо смогу получить доступ к полям класса, либо мне придётся реализовывать всё самому, и ничего мне не поможет. Да, если на вход объекта передавать интерфейс, а не конкретный класс, то гораздо проще объект заменить, но мне не нужно его заменять.
                                                          • 0

                                                            Да никто ведь не говорить, что это невозможно. Код компилируется, тесты проходят, все запускается и даже выдает приемлемые результаты. В татье высказано мнение, что это небезопасно. В комментах приведено достаточно много примеров это подтверждающих. Некоторые участники дискуссии считают, что этого достаточно, чтобы отказаться от наследования реализации там, где это возможно и пользоваться наследованием интерфейсов/шаблоном "декоратор" (для обеспечения полиморфизма) и инкорпорацией (для повторного использования кода). Некотрые считают что это возможно во всех случаях. В любом случае, решать как именно поступать придется вам самим.
                                                            В примере с UndoStack нужные вам потроха класса #внезапно могут оказаться privat. Ведь автор "или по незнанию или по злому умыслу" не предусмотрел наследования.

                                                            • 0
                                                              В статье даны выводы 3 и 4, призывающие запрещать наследование реализаций по умолчанию. Вот и скажите мне, к чему призывает статья.

                                                              Если так, тогда решений, отличных от саморучной реализации всех классов, просто не будет, и механизм коммитов не будет связан со стеком команд.
                                                              • 0

                                                                К запрету наследования того, что не не может быть унаследовано безопасно. "По умолчанию" вероятно следует понимать так: "Должно быть запрещено наследование всего, что не готово к наследованию. То, что готово к наследованию должно быть отмечено явным образом".
                                                                Вообще на данный момент была показана опасность только перекрытия виртуальных методов в классах потомках. Наследование невиртуальных методов на данный монмент представляется безопасным.

                                                                • 0
                                                                  И вместо рекомендации по ослаблению ограничений, вы отстаиваете рекомендации усиливать эти самые ограничения. Вместо рекомендации по предпочтению protected над private и выносу кода из виртуальных методов в обычные, оставив в виртуальных только набор вызывов невиртуальных методов, вы советуете (или отстаиваете рекомендации) отказываться от наследования в пользу реализации интерфейсов. Которые не более безопасны, чем наследование классов, только их небезопасность выводится за пределы вашей ответственности, как проектировщика. И контракты — это только попытки описать способы обезопасить себя, не более.
                                                                  • 0

                                                                    Нет. Я советую если "код из виртуальных методов не вынесен в обычные..." и/или не приняты еще какие-то специальные меры предосторожности по возможности отказаться от использования наследования в пользу инкорпорации + использования интерфейсов. Или не отказываться и принять на себя все риски.
                                                                    Про небезопасность наследования интерфейсов очень хотелось бы послушать подробнее, с примерами. Лучше знать обо всех граблях, уоторыми устлан наш нелегкий путь.

                                                                    • 0
                                                                      Да только ода проблема. Никаких гарантий.
                                                                      К примеру, вы сделали интерфейс потока данных. И ожидаете, что если запись не пройдёт успешно, вам возвратят ошибку. А вам, допустим, выбрасывают исключение, которое кладёт всё приложение, ведь его никто не ловит. Формально вы не виноваты.
                                                                      Ладно, это пол-беды. другой пример. Вы в вашей стратегии ждёте контейнер для хранения объектов. И ожидаете, что вы сможете в него сбрасывать ссылки на временные объекты. Последнее, что вы ожидаете — это что у контейнера будет глобальное состояние, и объекты не умрут после удаления самого хранилища. В этом случае формально виноват будет пользователь, хотя он не нарушит никаких соглашений. Ведь в интерфейсе, который вы ожидали, не написано, как должны храниться объекты на протяжении жизни экземпляра приложения. С наследованием такая херня не пройдёт, контейнер выполняет свой деструктор, который выполнит деструкторы всех объектов, и даже если в глобальном пространстве сохранятся ссылки, они будут битыми и относительно безвредными.
                                                                      Ладно, у нас же камень преткновения — виртуальные методы. Так и быть, пусть у нас есть класс фигуры. И у него есть виртуальный метод для вычисления центра масс. «Стратегия вычисления центра масс!» — возразите вы. И не сказать, что ошибётесь. Но есть одно но. В простейшем случае центр масс зависит от геометрической формы. Однако он может зависеть от распределения плотностей… И от скорости… И от импульса… Ускорения… Энергии… Так что будет принимать стратегия на вход? Какой у неё интерфейс? Снова перекладываем всё на шею пользователя? Самое грубое решение. В чём же преимущество виртуального метода? В том, что не нужно придумывать интерфейс стратегии, доступ ко всем нужным данным уже имеется. Почему бы нам не сделать доступ ко всем этим данным из интерфейса? Ну, можно, и даже нужно, если это какой-нибудь физический движок. А если нет? Или, если, например, мы не можем предоставить однородный доступ к данным, что тогда? Например, если у нас фракталы есть. Ведь для отрисовки фрактала не нужно знать его форму, достаточно отрендерить его в текстуру и натянуть на AABB, а центр масс вычислять по аналитической аппроксимации. Чисто теоретически, всё это можно сделать на чистых интерфейсах. Будет ли это проще? Будет ли меньше ошибок?

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

                                                              Что?

                                                              > Если UndoStack вне области, доступной для модификации (как минимум, по банальной причине бинарной совместимости), ваш подход мне ничего не даст.

                                                              Мой опыт говорит, что в UndoStack сами команды всё равно приватные (в крайнем случае internal), наследованием там ничего не поправить. Если код не задуман для расширения — наследование не спасёт.

                                                              Если вы меня хотите убедить, что наследование можно использовать как грязный хак ради «паблика Морозова» — соглашусь, можно.
                                                • 0
                                                  Про последний абзац, в том числе эту идею развивает Егор Бугаенко (yegor256.com), если не слышали о нём рекомендую ознакомится с 105 выпуском подкаста «Разбор полетов».
                                                  • +2

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

                                                  • 0
                                                    Тогда для более-менее серьезного переопределения поведения понадобится создавать сразу несколько аспектов, которые надо еще и как-то инкапсулировать в один «набор аспектов, реализующих вот такое вот поведение». При этом каждый аспект сам по себе будет являться наследником виртуального класса. В конечном итоге мы приходим к куда более сложной и многословной реализации той же самой мысли.
                                                    • +1
                                                      Если кто не понял, то напишу это же проще. Не используйте «Шаблонный метод», а используйте «Стратегию». :)
                                                      • 0
                                                        Одно без другого не получается, скорее не реализуйте «Шаблонный метод» используя наследование
                                                      • 0
                                                        «Часто забывают, что ООП — это от слова „объект“, а не от слова „класс“ ».
                                                        Часто забывают, что класс это тоже объект…
                                                        • 0
                                                          Два чаю! К сожалению, а может и к счастью, это утверждение не всегда верно.
                                                      • 0
                                                        И что же теперь, ограничиваться одним уровнем наследования от абстрактного класса?

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


                                                        Внимание, вопрос: с точки зрения автора статьи, как обходиться в таких ситуациях?

                                                        По мелочам обычно выручает декоратор. Кода получается больше (правда 95% шумового кода декоратора все равно нагенерит решарпер), но зависимость ослабляется до уровня публичного интерфейса. Заодно можно бесплатно комбинировать несколько декораторов.

                                                        • +1
                                                          Как будто что-то плохое. Кто хоть раз ковырялся в семи уровнях наследования WPF, тот меня поймет.

                                                          Один пример плохой реализации не может быть доказательством несостоятельности концепции. Например, иерархия классов виджетов Qt вполне себе адекватна и красива.

                                                          По мелочам обычно выручает декоратор

                                                          Так а в чем профит то? Если мне нужно дополнить всего один метод, то в случае с наследованием я переопределю всего один метод: вызову реализацию предка и допишу пару частных операторов. В случае с декоратором мне придется писать класс-декоратор, который будет либо унаследован от базового декоратора (что следуя вашей логике плохо), либо продублирует код базовой реализации (что уж точно нехорошо).
                                                          • +1
                                                            Один пример плохой реализации не может быть доказательством несостоятельности концепции. Например, иерархия классов виджетов Qt вполне себе адекватна и красива.

                                                            И чем же плохо ограничение, препятствующее плохой реализации и облегчающее хорошую?


                                                            Так а в чем профит то?
                                                            Вы точно читали статью и мой предыдущий комментарий?

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


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

                                                            Почему вы проигнорировали композицию с делегированием? Вы точно знаете, что такое декоратор?

                                                            • 0
                                                              просто посчитайте объем строк кода и число программных сущностей при реализации через наследование и при реализации через декорирование/делегирование/композицию
                                                              • +1

                                                                Разница между наследованием и композицией:


                                                                1. Минус одно наследование.
                                                                2. Плюс одно приватное поле
                                                                3. Плюс "шумовой" делегирующий код (можно избавиться используя динамическую типизацию или метапрограммирование).

                                                                Пункты 2 и 3 — экономия при написании кода, пункт 1 — при сопровождении. Итоговый баланс немного предсказуем.

                                                                • +4
                                                                  Я вот искренне не понимаю, зачем нужен пункт 3? Зачем использовать какой-то кодогенератор, когда разработчики языка позаботились об этой ситуации заранее и предоставили тебе наследование? Ведь это упрощённый способ записи делегации. Без бойлерплейта. Из каробки. Может называется по-другому, но работает точно так. А если что-то плавает как утка и крякает как утка, то это и есть утка.
                                                                  • 0
                                                                    Bonart говорит о гибкости и корректности в сложном случае, а вы — о простом случае. Может быть поэтому вы друг друга не понимаете?
                                                                    • –1

                                                                      Наследование как "упрощенная запись делегации" — ложь.
                                                                      "Без бойлерплейта" — ложь.
                                                                      "Работает точно так" — снова ложь.
                                                                      Контрольный вопрос — как скомбинировать функционал нескольких наследников с мелкими модификациями/дополнениями функционала?
                                                                      С декораторами это делается элементарно.

                                                                      • 0
                                                                        ``Наследование как «упрощенная запись делегации» — ложь'' — ложь
                                                                        ``«Без бойлерплейта» — ложь'' — ложь
                                                                        ``«Работает точно так» — снова ложь'' — ложь

                                                                        > Контрольный вопрос — как скомбинировать функционал нескольких наследников с мелкими модификациями/дополнениями функционала?

                                                                        Мультинаследование.
                                                                        • 0
                                                                          Мультинаследование
                                                                          Недоступно в большинстве языков.
                                                                          • +1
                                                                            Ну а где-то не доступны декораторы, и динамическая генерация (байт)кода. Там, где уже всё есть — можно пользоваться готовым наследованием. Там, где нет — придумывать замену. Но, очевидно, не надо говорить о том, что наследование не нужно, если оно уже реализовано. Хорошо реализованное наследование решает множество проблем композиции без необходимости задействовать кодогенерацию. С другой стороны, можно как в лиспе наследование реализовать через кодогенерацию (метаобъектный протокол). Заодно получить возможность реализовывать разные механизмы наследования в зависимости от выбора метаобъектного протокола. Но какова бы не была реализация, принцип остаётся в силе — один и тот же, и для наследования, и для метагенераторов.

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

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

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

                                                                        • +1

                                                                          А вот про эти языки можно поподробнее. Хотя бы список.

                                                                          • 0

                                                                            Ну, полный список я Вам вряд ли предоставлю. Но для примера в Ruby есть def_delegator для этого, некоторые используют delegate из ActiveSupport, хотя это уже ближе к кодогенерации.
                                                                            Кстати, аналогичную кодогенерацию можно устроить в любом языке где есть compile-time макросы. Rust, Crystal, Nim, etc. к вашим услугам. В Crystal такое есть из коробки, про остальных точно не знаю. Но сделать аналог весьма просто.
                                                                            В функциональных языках делегация тоже хорошо поддерживается, только там она осуществляется от модуля к модулю, например defdelegate в Elixir.

                                                                            • 0

                                                                              Спасибо! Интересна была как раз реализация средствами языка, а не макросами/препроцессорами. И мне тут привели хороший пример.

                                                                              • +2

                                                                                Так а в чём разница то? В языках, где есть нормальные макросы (если у Вас это слово ассоциируется с C++, то забудьте эту ассоциацию), абсолютное большинство средств языка выражается именно через макросы, включая элементы синтаксиса.
                                                                                В Kotlin, насколько я понимаю, пока очень урезанная поддержка делегации — либо всё делегировать, либо ничего.

                                                                                • +2
                                                                                  Не обольщайтесь, та же самая кодогенерация, даже хуже, потому что встроена в язык. AFAIK в Ruby ещё можно перехватывать вызовы несуществующих методов, можно сделать делегирование через эту фичу.
                                                                                  • 0

                                                                                    В дотнете тоже можно так, через DynamicObject, ну или RealProxy.

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

                                                                                      т.е. препроцессор встроен в компилятор и развертывает эти констркуции в код на Kotlin?


                                                                                      AFAIK в Ruby ещё можно перехватывать вызовы несуществующих методов, можно сделать делегирование через эту фичу.

                                                                                      В PHP тоже можно, но это совсем другая история.

                                                                                      • 0
                                                                                        Нет, на выходе сразу class-файл, но под капотом там конечно сгенерированные методы.
                                                                                        • 0

                                                                                          Насколько я понял, вопрос был не про то, что на выходе, а про детали процесса компиляции… остаётся ли делегирование самостоятельной сущностью после синтаксического анализа или преобразуется в эквивалент набора методов на уровне AST?
                                                                                          Правда, непонятно, какая разница в плане практического применения? По таким критериям языки можно делить если только из теоретического интереса, тем более что реализация компилятора может меняться от версии к версии.

                                                                                          • 0

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


                                                                                            for(i=1; i<10; i++){...}

                                                                                            в такой же код как и


                                                                                            i=1; while (i<10) {...; i++}

                                                                                            это прямая компиляция или встроенный препроцессор? Грань тонка… И важно ли это с практической точки зрения? Мне кажется нет.

                                                                                            • 0

                                                                                              Ключевая разница в том, куда будет указывать сообщение об ошибке. Если сообщение малопонятно и/или указывает в сгенерированное нечто, то это кодогенерация. А если указывает на реальный исходник и понятным же сообщением об ошибке, то вполне себе полноценная компиляция.

                                                                                              • 0

                                                                                                Вот что получилось. Даже не знаю, как это оценить.


                                                                                                Заголовок спойлера

                                                                                                Делегирующий метод в стеке вызовов есть, но ссылки на строку исходного кода нет. Любопытно, как поведет себя отладчик. Вероятно провалится сразу в делегируемый метод. Получается, что все-таки прямая компиляция?


                                                                                                Exception in thread "main" java.lang.ArithmeticException: / by zero
                                                                                                    at Base.test(Delegation.kt:6)
                                                                                                    at Derivative.test(Delegation.kt:-1)
                                                                                                    at DelegationKt.main(Delegation.kt:20)
                                                                                                • 0

                                                                                                  По вашей ссылке у меня не выдаёт никаких исключений.

                                                                                                  • 0

                                                                                                    Это все потому, что я не умею работать с try.kotlinlang.org
                                                                                                    Попробуйте еще раз по той же ссылке

                                                                                                    • 0

                                                                                                      А что вы изменили? Я там вроде всё прокликал тогда, а стектрейс так и не появился.

                                                                • 0
                                                                  Для выполнения пункта 2 в точном соответствии с пунктом 3 классу-потомку необходима полная информация о времени вызова и реализации перекрытого виртуального метода

                                                                  Достаточно иметь контракт переопределяемого метода. А вызываться он может как угодно в соответствии с контрактом.
                                                                  Вам же не требуется для реализации метода интерфейса знание где и как он будет вызываться.

                                                                  Например реализовываете вы собственный AbstractSpliterator. Вы можете реализовать только 1 метод в соответствии с контрактом и вообще не разбираться как оно там работает внутри (а работает оно хитро). Можете реализовать 2 или даже 4, если хотите улучшить производительность. Но даже в этом случае знать вам надо только контракты методов, а не то как они используются.
                                                                  • 0
                                                                    Точно, сейчас я считаю что большинство выстрелов по ногам происходит из-за нарушения контракта методов, в том числе при переопределении, или их отсутствия.
                                                                    • 0
                                                                      А можно не переопределять, а использовать пустые параметры и что-то вроде func_get_args() (PHP) :)
                                                                      • 0
                                                                        В смысле? Я не понимаю при чем тут это.
                                                                        • 0
                                                                          Тогда скажите, что Вы понимаете под «нарушение контракта методов»? Может я Вас не понял :)
                                                                          • 0
                                                                            https://goo.gl/b4Se5e
                                                                            • 0
                                                                              Тогда я говорил об этом:
                                                                              «В объектно-ориентированном программировании контракт метода обычно включает следующую информацию:
                                                                              возможные типы входных данных и их значение;»

                                                                              Объявив пустые параметры и использовав func_get_args() можно не править принимаемые параметры в наследнике вслед за родителем.
                                                                              • 0
                                                                                1. Я пишу на статически типизированном языке и не хочу вручную проверять типы.
                                                                                2. Если типы/кол-во аргументов изменилось это новый метод (перегрузка) со своим контрактом и можно с чистой совестью его описать.
                                                                                3. Меня распнут за такой код :)
                                                                                • 0
                                                                                  1. Меня распнут за такой код :)

                                                                                  Вы гуманист! За такой код полагается только это.