Комментарии 160
Для учета перемещений животных в пространстве возможно так не получится, потому что метод "летать" вызовет проблемы и костыли.
Однако сама кинематика — сходная. Пингвины замечательно летают, но не в воздухе, им требуется в 1000 раз более плотная среда для полёта.
Если мы исследуем яйценоскость, то вообще не важно кто как летает. Т.е. вопрос о наследовании можно ставить только после прояснения задачи, не ранее.
о, геймдев подоспел
Имхо, продумывать наперед лучше только в том случае, если примерно известно, куда дует бизнес. Иначе с большой вероятностью окажется, что архитектура неверно заложена не под те кейсы, в итоге придется делать тройную работу: 1) трата времени на первоначальную архитектуру 2) переделывание на новую 3) собственно, добавление фичей
У вас программа по учету яйценоскости может быть. Зачем вы будете вводить летные интерфейсы? При чем тут самолеты?
> Автор просто учебник до интерфейсов еще не дочитал.
прямой переход на личности
то тут же потребуют реализовать птичий метод «лети()», а пингвины летать не умеютНу и ладно.
throw "Crashed.";
throw new NotImplementedException();
p.s. NotSupportedException
а что, а вдруг?
в си-образных что только не возвращают: и минус единицу, и специальные резулты, и ошибки кидают, и наллы…
в Смолтоке сделали проще: на непонятные сообщения возвращать нотАндестенд
и все, если птицы как в ангрибердз смогут воспользоваться для полета катапультой, то это их право!
Заодно всем прописать дефолтовый отказ на рытьё нор, плаванье и фотосинтез
а что, а вдруг?
На счёт фотосинтеза Вы ошибаетесь. Фотосинтез не был характерен ни для кого из предков пингвина. Полёт же был им утрачен.
А относительно рытья и плавания — ваше предложение вполне разумно, т.к. такое поведение у любых животных присутствует чаще, чем отсутствует.
для пингвина много что нехарактерно
это просто значит, что он не может в естественной среде стать участником задач, в рамках которых ему предъявят такие требования
а медведи, например, нечасто по лесу на велосипедах ездят
я к тому веду, что возможно, всё-таки было бы проще и логичнее уточнять интерфейсы под экосистему задачи, а не плодить дефолтные строчки?
Что нарушает LSP
Вы что, у себя в контракте перечисляете все исключения, которые не может выбросить метод?
Автор коммента, на который я ответил, явно не думал, что нужно менять контракт птицы, если это вообще возможно.
А так то да, если у птицы заранее заложено исключение CantFly, то проблемы нет.
Если у вас конечно пингвин и голубь, то тут проще, а если у вас пару сотен видов птиц вы будете в каждом классе заново реализовывать интерфейс? Учитывая что большая часть птиц будет иметь почти одинаковые методы для полёта, ходьбы, еды и тп. Нет кончено вы накидаете пару абстрактных классов и будете с ними жить, наследуясь от них.
Ох уж этот идеальный мир без наследования и проблем вызванных им.
Нет кончено вы накидаете пару абстрактных классов и будете с ними жить, наследуясь от них.Можно запилить «композитную» птицу из разных компонентов, например.
Если у вас конечно пингвин и голубь, то тут проще, а если у вас пару сотен видов птиц вы будете в каждом классе заново реализовывать интерфейс? Учитывая что большая часть птиц будет иметь почти одинаковые методы для полёта, ходьбы, еды и тп.
Делаете этот общий метод один раз и, например, передаёте его в конструктор, так вообще можно даже 100 типов не создавать и отдельно не прописывать.
А что не так? Квадрат есть прямоугольник, и наследование идет в обратную сторону. А если у вас прямоугольник — подкласс квадрата, вы что-то делаете не так.
С точки зрения математики все однозначно: квадрат = прямоугольник у которого все стороны равны, но за такую иерархию объектов по головке точно не погладят.
Квадрат есть прямоугольникТесты не проходят.
Rect rect = new Square(5);
float aspectRatio = 2/3;
rect:setAspectRatio(aspectRatio);
assert(rect:getAspectRatio() == aspectRatio);
Либо зависимые типы, когда объект класса Rect можно использовать как Square, что зависит от значений этого Rect.
а "я квадрат" должно быть просто его свойством
Тогда нельзя будет написать методы, которые принимают только квадраты (и это контролирует компилятором)
либо у класса "прямоугольник" не должно быть метода "setAspectRatio"
Не поможет. Метод "возвратить прямоугольник с вдвое большей длиной и той же шириной" будет ломаться на квадратах, унаследованных от прямоугольника, даже если весь тип "прямоугольник" — иммутабельный.
"Вернуть радиус вписанной окружности"
отказаться от наследования от прямоугольника к квадратуЭто и есть верное решение. В ООП квадрат и прямоугольник не являются друг другом, у них просто есть похожие черты интерфейса и детали реализации. Настораживает даже одна только необходимость хранить лишнее поле в квадрате.
Тогда не делать в прямоугольнике метод setAspectRatio
А его и нет. Есть только конструктор. Rect(a,b)
Упомянутая функция удвоения длины просто создает новый и имеет тип Rect -> Rect
Если Square унаследован от Rect, она тоже будет работать (передали квадрат, а получили прямоугольник), но вот пользователь функции, в зависимости от того, как язык устроен, может ожидать, что оно Square->Square
Вписать окружность так-то можно в любую фигуру.
Ну и ладно, пофиг на соотношение сторон. Что насчет такого теста, банальный конструктор копирования:
Rect rect1 = new Rect(5, 4);
Rect rect2 = new Square(rect1);
Rect rect3 = new Rect(rect2);
assert(rect1.area() == rect3.area());
Вообще, квадрат на попытку растянуть должен ругаться "квадрат есть объект с aspectRatio==1.0", а уж если вам надо, чтобы ваши прямоугольники все были растягиваемы, выполняйте try-catch и отдавайте наружу, что вам передали нерастягивающийся прямоугольник.
По мне, математическое наследование просто не совпадает с программистским, и если "математически" можно унаследовать пингвина от птицы, то программистски неизбежны интересные грабли.
Вообще, квадрат на попытку растянуть должен ругаться «квадрат есть объект с aspectRatio==1.0»Квадрат вообще не должен наследоваться от прямоугольника. Потому что у прямоугольника есть свойства width и height, и квадрат их унаследует. И вот как менять квадрату размер, чтобы в процессе он не бросал «квадрат есть объект с aspectRatio==1.0»? Одновременно присвоить width и height мы не можем, а значит нужно городить отдельный костыль, чтобы менять стороны квадрата унаследованного от прямоугольника. Только зачем он тогда вообще унаследован?
параллелепипеды
Параллелограммы.
А ещё их можно интерпретировать как подмножество трапеций.
Так что параллелограмм считать подвидом трапеций уже получается плохо.
Да, после пояснений, полностью с Вами согласен. Подмножеством трапеций из параллелограммов являются только прямоугольники.
Кстати, выпуклые 4-угольники с перпендикулярными диагоналями, но не всеми равными сторонами — тоже как-то называются.
Ну почему же косяк? Просто эта точка несобственная, а продолжения боковых сторон и прямая, проходящая через середины оснований, — параллельны.
То, что параллельные прямые не обязаны проходить через центры оснований.
Как и прямые, проходящие через точку пересечения продолжений сторон.
Смотрите, мы задаём пучок прямых, которому принадлежат продолжения сторон. Прямая, проходящая через середины оснований — тоже принадлежит этому пучку, независимо от того, собственная у него точка пересечения или несобственная.
Потому что для начала вообще-то квадрат есть и прямоугольник, и ромб, а всё это вместе — параллелепипедыВ математике — да, но это не повод переносить эту иерархию в код.
Я считаю что прямоугольник, ромб, и квадрат должны быть реализованы независимыми классами, которые реализуют иерархию интерфейсов с нужными клиенту свойствами, в зависимости от требований (например Фигура2Д > Многоугольник2Д > ВыпуклыйМногоугольник2Д и т.д.). В довесок можно добавить вспомогательные классы вроде точек и AABB, для случаев когда нужен более низкий или высокий уровень абстракций.
В таком случае сохраняется статическая типизация, нет лишних полей, нет озвученных в этой и соседних ветках проблем. Дублирование кода при этом незначительно или легко устраняется вынесением общих формул в статический класс геометрии.
Всё это от непонимания вариантности. Прежде всего среди самих создателей компиляторов. Вот в D компилятор умеет корректно выводить вариантность параметров.
p.s. А про гомеоморфные преобразования вы что-нибудь слышали? Кружка = бублик ( ru.wikipedia.org/wiki/Гомеоморфизм )
Если мы к единице применяем N, то она становится двойкой.
Тут есть нюанс, связанный со словом "становится". Это означает, что во всех местах, где использовалась эта единица, теперь используется двойка. Это создает проблемы для уравнений типа "x = 1*x".
Если же она становится двойкой только в этом выражении, а все другие выражения продолжают работать с единицей, то это ничем не отличается от ситуации, когда метод setAspectRatio() некоторого объекта возвращает другой объект.
Вообще это сводится к понятию "ссылка", которую математика не рассматривает (из-за чего и появляются сложности типа как создать множество всех множеств, которое содержит само себя), в ней фигуры просто задаются, если фигура поменяла размер, значит мы задали другую фигуру.
А в остальном — капитанская заметка, не претендующая на звание статьи…
Нет бы рассмотреть корректные варианты реализации, когда и ООП нормальное и мозг не ломается?
посмотрите на результаты опроса.Собственно, какой вопрос, такие и ответы…
Если нет других вводных в вопросе, то и не троньте птицу, а то утконос уже нервничает!
А квадрат является прямоугольником (математики так сказали). А теперь пробуем предьявить реализацию методов "изменить длину стороны", чтобы квадрат не ломался.
Собственно, проблема давно и подробно описана, только в учебниках по ООП ее не любят задевать.
Зато любое свойство (теорема), что выполняется для прямоугольника, выполняется и для квадрата. Эта та самая разница между онтологическим наследованием и наследованием типов, что ниже упоминалась
Так это и не проблема ооп, а проблема мутабельности. С иммутабельными сущностями вы можете хоть квадрат от прямоугольника наследовать (сужающая подтипизация), хоть прямоугольник от квадрата (расширяющая подтипизация).
https://habr.com/ru/post/351730/
Всё уже сказано до вас.
Есть три типа наследования.
Онтологическое наследование указывает на специализацию: вот эта штука — специфическая разновидность той штуки (футбольный мяч — это сфера и у неё такой-то радиус).
Наследование абстрактного типа данных указывает на замещение: у этой штуки такие же свойства, как у той штуки, и такое-то поведение (это принцип подстановки Барбары Лисков).
Наследование реализации связано с совместным использованием кода: эта штука принимает некоторые свойства той штуки и переопределяет или дополняет их таким-то образом. Наследование в моей статье «О наследовании» именно такого и только такого типа.
Это три разных и часто противоречивых отношения. Требовать любого или даже всех не представляет никаких сложностей. Но требование поддержки одним механизмом двух или более из них — значит нарываться на проблемы.
Насколько я понимаю наследование было придумано программистом как механизм для повторного использования кода. И это было неплохо. А потом пришли математики и заявили, дескать ваша игрушка в ООП похожа на наши Абстрактные Типы Данных, и вы все делаете неправильно. Этот лозунг был воспринят как догма. Поэтому теперь доверчивые программисты вынуждены заниматься поиском глубинного смысла, вместо того чтобы просто писать и реюзать код как им удобно.
pikabu.ru/story/boevyie_kenguru_3530510
Я вам больше скажу, можно даже от моллюска унаследовать. Абы польза была. Можно все, что не запрещает язык. И соглашения. И здравый смысл.
Это точно не на основании опросов решать надо, не тот случай когда помощь зала позволит стать миллионером.
Для учета перемещений животных в пространстве возможно так не получится, потому что метод "летать" вызовет проблемы и костыли.
У настоящих пигнвинов же не вызывает. Вполне себе машут крыльями, есть интерфейс и реализация. А то что аэродинамика у них другая, ну это не баг, а фича.
Объявите базовый абстрактный класс GenericBird, от него наследуйте летающих, ходячих, плавающих птиц, а также уток. Например.
class Bird:
have_feathers = True
class FlyingInTheAirMixin:
pass
class Penguin(Bird):
pass
class Eagle(Bird, FlyingInTheAirMixin):
pass
Ну был же такой мультфильм, где этот вопрос обсуждался.
"Кто такие птички?" назывался.
С точки зрения биологии, есть вполне строгое и формальное определение того, что организм относится к классу птиц.
Ага, ага. В биологии птиц выделили в отдельный класс, прямо как и в С++ .(класс называется CAves).
Млекопитающих относят к другому классу (CMammalia).
А для рыб пришлось придумать аж два класса. Потому что хрящевые и костные рыбки — это две очень большие разницы.
Так вот, у любого экземпляра класса CAves есть три обязательных признака:
- Наличие позвоночника в скелете. Это свойство наследуется от базового (даже не класса, а подтипа, который называется TVertebrata)
- Развитие зародыша в яйце, вне материнского организма. (Потому что живородящих птиц в природе не существует).
- Теплокровность, причем обязательно истинная. (Если про эту мелкую деталь забыть, то можно нечаянно и крокодила к птичкам отнести).
Птицы, в отличие от крокодилов, поддерживают температуру тела исключительно только благодаря биохимическим процессам, происходящим в организме. Метаболизм называется.)
Все остальные свойства (наличие крыльев, перьев, умение летать, петь и пр.) в общем случае не являются обязательными.
Например в природе существуют птицы без перьев (Вместо перьев у них некоторое подобие шерсти). ~
Ну и про птиц, не умеющих летать, тоже можно вспомнить.
Вроде все просто и понятно.
Тот, кто все это придумывал, решил что летать могут не только птицы. (Чем насекомые хуже?). Летать умеют не только птицы. Этим свойством обладают даде некоторые млекопитающие. (Летучие мыши, например).
Так что если в программной модели метод Fly есть только у класса CAves, то… Надо порефакторить.
Общая идея все равно очевидна:
Для того, чтобы грамотно сконструячить архитектуру приложения вообще (и иерархию классов с их методами / свойствами / интерфейсами в частности) нужно как минимум неплохо разбираться в предметной области. Или, в крайнем случае, постоянно задавать вопросы тем коллегам, кто разбирается.
Это просто и понятно, но ведь очень мало программ будут руководствоваться этими критериями (потому что многим предметным областям никакого дела нет до наличия позвоночника и до теплокровности)
Так может правильнее вызывать не метод "лети", а метод "маши крыльями". А метод "лети" должен обрабатываться физикой (сарказм).
Скажем честно, на статью не тянет, скорее просто реплика. Причем на вечную тему "докопаться можно до всего". Для почти любого очевидного случая можно найти исключение. Это будет просто неиссякаемый источник "статей".
— Можно ли варить борщ в кастрюле?
— Можно
— Вы не прошли! Борщ можно варить в котелке!
Отличный, просто прекрасный вопрос для собеседования. После него можно сразу вставать и прощаться.
И отвечу на вопрос. От нормальной, абстрактной птицы пингвина наследовать можно и нужно.
Причем тут несчастные пингвины? Даже летающие птицы не летают некоторое время после рождения. Эта способность появляется чуть позже. У некоторых она не появляется совсем. А есть и такие, у которых она может пропадать — временно из-за травм или насовсем. Дохлые птицы тоже не летают, но остаются птицами в нашем понимании.
Напомнило, как MS унаследовал "пингвина от птицы". Array реализует ICollection, а у ICollection есть метод Add, который у Array-ев бросает эксепшн в самых неожиданных ситуациях (например, в глубинах EF Core).
Для учета перемещений животных в пространстве возможно так не получится, потому что метод «летать» вызовет проблемы и костыли.
Надо правильно спроектировать дизайн, и не будет проблем. Нужен метод «Перемещение», который задает перемещение в пространстве. А уж каким образом это перемещение происходит, задается через свойства «Летает», «Плавает» и т.д.
класссический пример следования стереотипам, которые переносятся в программировние.
сразу сходу: не все представители класса "птицы" обязаны летать.
в повседневной жизни мы знаем как-то об эттм факте так или иначе. например, курица, индюк, страус, пингвин, киви (не фрукт),.
но почему-то в программированнии, раз ты птица — то обязана летать.
почему так?
потому что это упрощенная модель. птица — летает, рыба — плавает (частные случаи когда рыба ходит и летает — а такие таки имеются в природе, отбрасываются во имя унификации)
поэтому на самом деле, в исходном задаче про пингвина только один подвох: упрощение ввиду стереотипов.
те кто попял суть этого, прошли тест.
остальные будут рассуждать о том пингвин птица при каких условиях
Если тут есть биологи, то они точно скажут, что между птицей и пингвином, с точки зрения биологии еще есть всякие виды, семейства, подвиды и т.п.
Мы же, разработчики, используем абстракцию для решения конкретных задач конкретного проекта, глубины своих знаний и опыта, а не занимаемся сотворением цифровой копии мира.
Можно ли пингвина наследовать от птицы?