Pull to refresh

Comments 160

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

Однако сама кинематика — сходная. Пингвины замечательно летают, но не в воздухе, им требуется в 1000 раз более плотная среда для полёта.

Дело ж не в этом.
Если мы исследуем яйценоскость, то вообще не важно кто как летает. Т.е. вопрос о наследовании можно ставить только после прояснения задачи, не ранее.
Было время, когда наследование боготворилось. Теперь только попробуй унаследовать скажут «фу». Скажут надо же темплейтами делать.
UFO just landed and posted this here

ECS и никак иначе, только хардкор! Хотя нет, хардкор как раз через наследование, пожалуй, и выйдет. "Стоя — и в гамаке"

UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here

Имхо, продумывать наперед лучше только в том случае, если примерно известно, куда дует бизнес. Иначе с большой вероятностью окажется, что архитектура неверно заложена не под те кейсы, в итоге придется делать тройную работу: 1) трата времени на первоначальную архитектуру 2) переделывание на новую 3) собственно, добавление фичей

Страус тоже летать не умеет, там не менее он птица! Так что метод «лети» на для всех птиц работает.
UFO just landed and posted this here
Вы поняли о чем статья вообще? Перечитайте еще раз. Все зависит от задачи, никогда нельзя однозначно ответить.
У вас программа по учету яйценоскости может быть. Зачем вы будете вводить летные интерфейсы? При чем тут самолеты?

> Автор просто учебник до интерфейсов еще не дочитал.
прямой переход на личности
UFO just landed and posted this here
подходит, просто придется делать это на около звуковой скорости…
в статье об этом написано, поищите по слову «страус»
то тут же потребуют реализовать птичий метод «лети()», а пингвины летать не умеют
Ну и ладно.
throw "Crashed.";
правильнее будет:
throw new NotImplementedException();
Заодно всем прописать дефолтовый отказ на рытьё нор, плаванье и фотосинтез
а что, а вдруг?

в си-образных что только не возвращают: и минус единицу, и специальные резулты, и ошибки кидают, и наллы…
в Смолтоке сделали проще: на непонятные сообщения возвращать нотАндестенд
и все, если птицы как в ангрибердз смогут воспользоваться для полета катапультой, то это их право!
Заодно всем прописать дефолтовый отказ на рытьё нор, плаванье и фотосинтез
а что, а вдруг?

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

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

а медведи, например, нечасто по лесу на велосипедах ездят

я к тому веду, что возможно, всё-таки было бы проще и логичнее уточнять интерфейсы под экосистему задачи, а не плодить дефолтные строчки?
UFO just landed and posted this here

Вы что, у себя в контракте перечисляете все исключения, которые не может выбросить метод?

UFO just landed and posted this here

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

UFO just landed and posted this here
UFO just landed and posted this here

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

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

Делаете этот общий метод один раз и, например, передаёте его в конструктор, так вообще можно даже 100 типов не создавать и отдельно не прописывать.
UFO just landed and posted this here
И на этом месте заходят пары: квадрат-прямоугольник и элипс/окружность и такие: пс. что там с I am? =)
UFO just landed and posted this here
Ну если наследование прямоугольника от квадрата — не вызывает вопросов, то вопрос с пингвином стоять не будет (птица — пингвин, который может летать =) )
UFO just landed and posted this here

А что не так? Квадрат есть прямоугольник, и наследование идет в обратную сторону. А если у вас прямоугольник — подкласс квадрата, вы что-то делаете не так.

см. выше
С точки зрения математики все однозначно: квадрат = прямоугольник у которого все стороны равны, но за такую иерархию объектов по головке точно не погладят.
У прямоугольника может быть метод «растянуть по горизонтали»
UFO just landed and posted this here
Квадрат есть прямоугольник
Тесты не проходят.
Rect rect = new Square(5);
float aspectRatio = 2/3;
rect:setAspectRatio(aspectRatio);
assert(rect:getAspectRatio() == aspectRatio);
UFO just landed and posted this here

Либо зависимые типы, когда объект класса Rect можно использовать как Square, что зависит от значений этого Rect.

UFO just landed and posted this here
а "я квадрат" должно быть просто его свойством

Тогда нельзя будет написать методы, которые принимают только квадраты (и это контролирует компилятором)


либо у класса "прямоугольник" не должно быть метода "setAspectRatio"

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

UFO just landed and posted this here

"Вернуть радиус вписанной окружности"

UFO just landed and posted this here
отказаться от наследования от прямоугольника к квадрату
Это и есть верное решение. В ООП квадрат и прямоугольник не являются друг другом, у них просто есть похожие черты интерфейса и детали реализации. Настораживает даже одна только необходимость хранить лишнее поле в квадрате.
Тогда не делать в прямоугольнике метод setAspectRatio

А его и нет. Есть только конструктор. Rect(a,b)


Упомянутая функция удвоения длины просто создает новый и имеет тип Rect -> Rect


Если Square унаследован от Rect, она тоже будет работать (передали квадрат, а получили прямоугольник), но вот пользователь функции, в зависимости от того, как язык устроен, может ожидать, что оно Square->Square

UFO just landed and posted this here
Используйте абстрактный класс как предка всех фигур, например. Чем сильнее сова геометрии натягивается на глобус ООП, тем больше костылей требуется, чтобы ничего не порвалось.
UFO just landed and posted this here
Собственно, с пингвином из статьи то же самое.
Использовать хотя бы уже придуманную классификацию:
Домен: Эукариоты
Царство: Животные
Тип: Хордовые
Класс: Птицы
Отряд: Пингвинообразные
Семейство: Пингвиновые

— и не будет проблем с летанием, плаванием и беганием.

Вписать окружность так-то можно в любую фигуру.

Не всякая окружность внутри фигуры является вписанной в неё.

UFO just landed and posted this here
UFO just landed and posted this here
Я исхожу из вполне ожидаемых свойств прямоугольника, которые могут от него потребоваться в клиентском коде, и которые его сломают при наследовании квадрата. Все равно что сказать что огурец можно унаследовать от пулемета, только у него не должно быть методов «reload» и «fire». Или добавить флаг «я пулемет».

Ну и ладно, пофиг на соотношение сторон. Что насчет такого теста, банальный конструктор копирования:
Rect rect1 = new Rect(5, 4);
Rect rect2 = new Square(rect1);
Rect rect3 = new Rect(rect2);
assert(rect1.area() == rect3.area());
UFO just landed and posted this here
Это значит, что квадрат имеет унаследованный от прямоугольника конструктор копирования. Запретите его вызывать комментарием в коде?
UFO just landed and posted this here
Если правильно договориться на этапе проектирования, что от чего наследуется — этой проблемы быть не должно. Либо оно не скомпилируется (можно так, если в выбранной предметной области это недопустимо), либо можно создать квадрат, вписанный в прямоугольник, либо default'ный квадрат с единичной (или нулевой) площадью… ООП ведь не обязано автоматически делать всё хорошо и правильно?
UFO just landed and posted this here
Так то и в живой природе передать курицу в конструктор пингвина не получится ;) Несмотря на то, что какие-то свойства у них общие.
UFO just landed and posted this here

Вообще, квадрат на попытку растянуть должен ругаться "квадрат есть объект с aspectRatio==1.0", а уж если вам надо, чтобы ваши прямоугольники все были растягиваемы, выполняйте try-catch и отдавайте наружу, что вам передали нерастягивающийся прямоугольник.
По мне, математическое наследование просто не совпадает с программистским, и если "математически" можно унаследовать пингвина от птицы, то программистски неизбежны интересные грабли.

Вообще, квадрат на попытку растянуть должен ругаться «квадрат есть объект с aspectRatio==1.0»
Квадрат вообще не должен наследоваться от прямоугольника. Потому что у прямоугольника есть свойства width и height, и квадрат их унаследует. И вот как менять квадрату размер, чтобы в процессе он не бросал «квадрат есть объект с aspectRatio==1.0»? Одновременно присвоить width и height мы не можем, а значит нужно городить отдельный костыль, чтобы менять стороны квадрата унаследованного от прямоугольника. Только зачем он тогда вообще унаследован?
UFO just landed and posted this here
параллелепипеды

Параллелограммы.
А ещё их можно интерпретировать как подмножество трапеций.

UFO just landed and posted this here
Так что параллелограмм считать подвидом трапеций уже получается плохо.

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


Кстати, выпуклые 4-угольники с перпендикулярными диагоналями, но не всеми равными сторонами — тоже как-то называются.

UFO just landed and posted this here

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

UFO just landed and posted this here
То, что параллельные прямые не обязаны проходить через центры оснований.

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

UFO just landed and posted this here
Потому что для начала вообще-то квадрат есть и прямоугольник, и ромб, а всё это вместе — параллелепипеды
В математике — да, но это не повод переносить эту иерархию в код.

Я считаю что прямоугольник, ромб, и квадрат должны быть реализованы независимыми классами, которые реализуют иерархию интерфейсов с нужными клиенту свойствами, в зависимости от требований (например Фигура2Д > Многоугольник2Д > ВыпуклыйМногоугольник2Д и т.д.). В довесок можно добавить вспомогательные классы вроде точек и AABB, для случаев когда нужен более низкий или высокий уровень абстракций.

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

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

UFO just landed and posted this here

Всё это от непонимания вариантности. Прежде всего среди самих создателей компиляторов. Вот в D компилятор умеет корректно выводить вариантность параметров.

UFO just landed and posted this here
UFO just landed and posted this here
В смысле? Вы спросили «где в математике такие трансмутации происходят», вот я вам и пишу, что с помощью преобразования сжатия прямоугольник может стать квадратом и наоборот. Ну, в общем, вся цитата, про которую вы спросили верна.
UFO just landed and posted this here
Мы применяем вполне общеизвестное геометрическое преобразование, обладающее определенными свойствами. То, что вы написали просто описывает сдвиг по оси x на один, а не равенство 1 и 2. Более того, никто не утверждал, что при таком преобразовании фигуры останутся равными. Сжатие же — даже само слово говорит о том, что размер изменится.
p.s. А про гомеоморфные преобразования вы что-нибудь слышали? Кружка = бублик ( ru.wikipedia.org/wiki/Гомеоморфизм )
UFO just landed and posted this here
Как вам написали выше, математически это звучит именно так (правда используются слова «образ» и «прообраз») и никого не смущает. Можно погуглить «преобразования плоскости» (яндекс уточняет, что это геометрия, 10 класс).
UFO just landed and posted this here
UFO just landed and posted this here
Значит, эту модель мироздания на ООП не нужно натягивать.
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
Если мы к единице применяем N, то она становится двойкой.

Тут есть нюанс, связанный со словом "становится". Это означает, что во всех местах, где использовалась эта единица, теперь используется двойка. Это создает проблемы для уравнений типа "x = 1*x".
Если же она становится двойкой только в этом выражении, а все другие выражения продолжают работать с единицей, то это ничем не отличается от ситуации, когда метод setAspectRatio() некоторого объекта возвращает другой объект.


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

UFO just landed and posted this here
А также может нарушать ещё кучу принятых в разработке проекта правил. И что с того? Сказали написать реализацию — я предложил вариант в одну строку. Не нравится: уточняйте ТЗ и спецификации. Тем более мы даже не знаем о допустимости исключений в базовом классе (кстати, наследование, возможно, приватное, что формально не нарушит LSP), т.к. базовый класс птица может иметь что-то вроде целостности скелета птицы и даже летающие могут не полететь.
UFO just landed and posted this here
Скажем так: если в вашей доменной модели нельзя наследовать пингвина от птицы — у вас что-то пошло не так :)

А в остальном — капитанская заметка, не претендующая на звание статьи…
Нет бы рассмотреть корректные варианты реализации, когда и ООП нормальное и мозг не ломается?
Капитанская заметка, однако посмотрите на результаты опроса. Поэтому и написал собствено
А слабо было результат в текст вложить?
щас вложу. Хотя уже смысла нет, статья в глубоком минусе.
ну подумаешь, треть слишком категоричны, часть тех, кто за, наверно, несколько наивны. Нормальное такое распределение.

Повторюсь (хотя для этой заметки уже поздно, наверно), лучше бы показали, как грамотно писать ОО-модель так, чтобы не возникало подобных «каверзностей».
посмотрите на результаты опроса.
Собственно, какой вопрос, такие и ответы…
Пингвин – птица, так определили специалисты по фауне. Программист должен определить классу свойство non_stop_flight_distance=0 под потребности бизнеса.
Если нет других вводных в вопросе, то и не троньте птицу, а то утконос уже нервничает!

А квадрат является прямоугольником (математики так сказали). А теперь пробуем предьявить реализацию методов "изменить длину стороны", чтобы квадрат не ломался.
Собственно, проблема давно и подробно описана, только в учебниках по ООП ее не любят задевать.

UFO just landed and posted this here

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

UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here

Так это и не проблема ооп, а проблема мутабельности. С иммутабельными сущностями вы можете хоть квадрат от прямоугольника наследовать (сужающая подтипизация), хоть прямоугольник от квадрата (расширяющая подтипизация).

UFO just landed and posted this here
давно придерживаюсь парадигмы: как можно меньше абстракции за счет наследования. в реальной жизни требования слишком часто меняются и никогда заранее не известны в полном виде, оттого продумать хорошую абстракцию, способную легко и просто расширяться могут единицы. потому, даже если вы умеете составлять хорошие абстрактные конструкции, не факт, что ваши коллеги смогут также изящно ее поддерживать и не портить :)

https://habr.com/ru/post/351730/


Всё уже сказано до вас.


Есть три типа наследования.

Онтологическое наследование указывает на специализацию: вот эта штука — специфическая разновидность той штуки (футбольный мяч — это сфера и у неё такой-то радиус).

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

Наследование реализации связано с совместным использованием кода: эта штука принимает некоторые свойства той штуки и переопределяет или дополняет их таким-то образом. Наследование в моей статье «О наследовании» именно такого и только такого типа.

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

Насколько я понимаю наследование было придумано программистом как механизм для повторного использования кода. И это было неплохо. А потом пришли математики и заявили, дескать ваша игрушка в ООП похожа на наши Абстрактные Типы Данных, и вы все делаете неправильно. Этот лозунг был воспринят как догма. Поэтому теперь доверчивые программисты вынуждены заниматься поиском глубинного смысла, вместо того чтобы просто писать и реюзать код как им удобно.

UFO just landed and posted this here
Вопрос неверный. Правильно «нужно ли пингвина наследовать от птицы?». Ответ — зависит от ситуации.
Боевые кенгуру
Вертолетный симулятор/тренажер адаптировали для Австралии. Заказчик потребовал добавить стада кенгуру как деталь ландшафта. Исполнитель отнесся халтурно, сделал модель кенгуру и прилепил ее методом copy/paste на логику пехотинца, поменяв только модуль перемещения. В результате, когда вертолет на приёмке прошел над стадом кенгуру, оные рассредоточились, перегруппировались и на втором заходе сбили вертолет ракетой.
pikabu.ru/story/boevyie_kenguru_3530510

Я вам больше скажу, можно даже от моллюска унаследовать. Абы польза была. Можно все, что не запрещает язык. И соглашения. И здравый смысл.
Это точно не на основании опросов решать надо, не тот случай когда помощь зала позволит стать миллионером.

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

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

Объявите базовый абстрактный класс GenericBird, от него наследуйте летающих, ходячих, плавающих птиц, а также уток. Например.

UFO just landed and posted this here

Про уток я упоминал. Friend class сможет объяснить даже утконоса ;)

Тогда надо переписать все на языке с множественным наследием!

UFO just landed and posted this here

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


С точки зрения биологии, есть вполне строгое и формальное определение того, что организм относится к классу птиц.
Ага, ага. В биологии птиц выделили в отдельный класс, прямо как и в С++ .(класс называется CAves).
Млекопитающих относят к другому классу (CMammalia).
А для рыб пришлось придумать аж два класса. Потому что хрящевые и костные рыбки — это две очень большие разницы.


Так вот, у любого экземпляра класса CAves есть три обязательных признака:


  1. Наличие позвоночника в скелете. Это свойство наследуется от базового (даже не класса, а подтипа, который называется TVertebrata)
  2. Развитие зародыша в яйце, вне материнского организма. (Потому что живородящих птиц в природе не существует).
  3. Теплокровность, причем обязательно истинная. (Если про эту мелкую деталь забыть, то можно нечаянно и крокодила к птичкам отнести).
    Птицы, в отличие от крокодилов, поддерживают температуру тела исключительно только благодаря биохимическим процессам, происходящим в организме. Метаболизм называется.)

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


Вроде все просто и понятно.


Тот, кто все это придумывал, решил что летать могут не только птицы. (Чем насекомые хуже?). Летать умеют не только птицы. Этим свойством обладают даде некоторые млекопитающие. (Летучие мыши, например).
Так что если в программной модели метод Fly есть только у класса CAves, то… Надо порефакторить.

Вот именно это и надо было бы автору написать в своей заметке!

Общая идея все равно очевидна:


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

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

Так может правильнее вызывать не метод "лети", а метод "маши крыльями". А метод "лети" должен обрабатываться физикой (сарказм).

Скажем честно, на статью не тянет, скорее просто реплика. Причем на вечную тему "докопаться можно до всего". Для почти любого очевидного случая можно найти исключение. Это будет просто неиссякаемый источник "статей".


— Можно ли варить борщ в кастрюле?
— Можно
— Вы не прошли! Борщ можно варить в котелке!


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


И отвечу на вопрос. От нормальной, абстрактной птицы пингвина наследовать можно и нужно.

Причем тут несчастные пингвины? Даже летающие птицы не летают некоторое время после рождения. Эта способность появляется чуть позже. У некоторых она не появляется совсем. А есть и такие, у которых она может пропадать — временно из-за травм или насовсем. Дохлые птицы тоже не летают, но остаются птицами в нашем понимании.

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

Напомнило, как MS унаследовал "пингвина от птицы". Array реализует ICollection, а у ICollection есть метод Add, который у Array-ев бросает эксепшн в самых неожиданных ситуациях (например, в глубинах EF Core).

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

Надо правильно спроектировать дизайн, и не будет проблем. Нужен метод «Перемещение», который задает перемещение в пространстве. А уж каким образом это перемещение происходит, задается через свойства «Летает», «Плавает» и т.д.

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


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


но почему-то в программированнии, раз ты птица — то обязана летать.
почему так?


потому что это упрощенная модель. птица — летает, рыба — плавает (частные случаи когда рыба ходит и летает — а такие таки имеются в природе, отбрасываются во имя унификации)


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

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

«Правильность» ответа определит кодревьювер
Sign up to leave a comment.

Articles