Наследование на примере слона

kokoc88

Я тут подумал, как убедить программиста в том, что наследование - один из фундаментальных механизмов ООП,- нужно использовать с умом? Коментарии приветствуются...
Допустим, вы проектировали логистическую программу для зоопарка. Начали вы, как и полагается ООП специалисту, с интерфейса:

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

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

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

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

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

klyv

а небо - голубое!

kokoc88

а небо - голубое!
Про голубое небо я могу написать ещё один пост на тему "данные в атрибутах или аннотациях".

klyv

как сказал один мой знакомый, ждём продолжения: пост о том, что телефоны звонят, серия постов о том, что ковры лежат на полу, висят на стенах и бывают персидскими, собаки лают, а заточенные ножи острые

kokoc88

как сказал один мой знакомый, ждём продолжения: пост о том, что телефоны звонят, серия постов о том, что ковры лежат на полу, висят на стенах и бывают персидскими, собаки лают, а заточенные ножи острые
Я слабо понимаю, о чём ты хочешь сказать. Что не прочитал мой пост полностью? Я его пока что не собираюсь стирать, у тебя есть ещё время.

klyv

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

6yrop

С первоначальным утверждением согласен. Я его формулирую еще в более жесткой форме "Наследование это плохо! Его надо использовать, только когда виден значительный выигрыш." Ссылка на то, что это фундаментальный принцип ОПП – это хуета.
Но приведенное объяснение мне не показалось убедительным.
 
Короче, использование животных потянуло за собой все типы перевозок и базу данных в придачу.

Это не совсем точное утверждение, если перевозки не требуются, то зачем, вообще, классы ElephantOnCar и ElephantOnTrain? почему бы не ограничиться тем, что выше по иерархии?

Papazyan

Вот если бы у тебя был язык c другими свойствами (с duck typing или метаклассами таких проблем не возникло бы. Вывод - создание и решение проблем на пустом месте.

6yrop

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

Papazyan

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

kokoc88

я прочитал. тема избита, вопроса не увидел, выводы очевидны, слово "боян" неуместно в интеллектуальной беседе.
Так ты первое предложение перечитай. :) Мне надо убедить реальных людей, которые с пеной у рта будут доказывать, что я неправ. Просто потому, что вряд ли увидят сходство между перевозкой слона и, скажем, преобразованием выражения с условием if в SQL-ный WHERE.

kokoc88

Это не совсем точное утверждение, если перевозки не требуются, то зачем, вообще, классы ElephantOnCar и ElephantOnTrain? почему бы не ограничиться тем, что выше по иерархии?
Они там нарисованы абстрактыми. Что-то в роде abstract class Elephant : Animal. Я просто хотел сказать, что функций перевозки вообще не должно было быть в этом классе.

kokoc88

Вот если бы у тебя был язык c другими свойствами (с duck typing или метаклассами таких проблем не возникло бы. Вывод - создание и решение проблем на пустом месте.
Я не понимаю, почему ты категорично заявляешь, что таких проблем не возникало бы. В реальной жизни нас никак не спасает тот факт, что мы можем создать экземпляр (абстрактного) класса. Что на счёт читаемости кода? Да и автотестами такие умельцы наверняка покроют последние реализации, с ненужными функциями. Я сомневаюсь, что язык программирования спасёт от нарушения базовых принципов. Но всё-таки мне не хотелось бы об этом спорить именно в этой ветке, потому что это в любом случае будет малоаргументированным холиваром.

kokoc88

"Наследование это плохо! Его надо использовать, только когда виден значительный выигрыш." Ссылка на то, что это фундаментальный принцип ОПП – это хуета.
Понимаешь, тема о том, как бы на пальцах убедить человека, который тебе скажет: "Как это хуета? От него всегда выигрыш, вот тут и тут у меня наследование и всё круто. И вообще в книжке написано, что наследование - это фундаментальный механизм ООП. Так что иди ботай."

katrin2201

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

kokoc88

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

6yrop

Это не совсем точное утверждение, если перевозки не требуются, то зачем, вообще, классы ElephantOnCar и ElephantOnTrain? почему бы не ограничиться тем, что выше по иерархии?

Они там нарисованы абстрактыми. Что-то в роде abstract class Elephant : Animal. Я просто хотел сказать, что функций перевозки вообще не должно было быть в этом классе.
я хотел сказать, что само по себе наследование не тянет за собой дополнительный модуль, если в ElephantOnCar и ElephantOnTrain находится только логика перевозки, то коллега может их просто не использовать. Все что вы можете переиспользовать по идее должно сидеть Elephant и выше.

katrin2201

Да блин, откуда у рядового разработчика желание?
Рядовых разработчиков ни в коем случае нельзя допускать до таких критичных вещей =)
А если уж допустили, то пеняйте на себя, и беритесь за рефакторинг =)
//добавлено
Рядовым разработчикам вообще все пох. Они тебе в любом случае, пусть там твои доводы 200 раз будут убедительны, придумают пицот отмаз, лишь бы не признавать своей ошибки и ничего не делать. На конечный то результат им по большому счету пох.
С этим придется смириться и жить, рефакторя особые наиболее критичные места, которые окупятся в обозримом будущем.

kokoc88

я хотел сказать, что само по себе наследование не тянет за собой дополнительный модуль, если в ElephantOnCar и ElephantOnTrain находится только логика перевозки, то коллега может их просто не использовать.
Нет, там находится десяток методов, которые тянут за собой какие-то классы. Просто не использовать их не получается. Я просто прошу не приводить в пример языки программирования, в которых данный пример с одной абстрактной функцией Move не покрытый тестами и документацией, не вызовет проблем. Я сомневаюсь, что язык программирования нас спасёт, а так давайте считать, что речь идёт о разработчиках на C#

kokoc88

Рядовых разработчиков ни в коем случае нельзя допускать до таких критичных вещей =)
Проблема в том, что ты что-то спроектировал. Пусть даже использовал паттерны или ещё что-то. А теперь тебе надо убедить тех, кто будет писать код, что там лучше не наследование, а отдельный класс. Это я выбрал просто самый банальный пример из всех возможных. У кого-то есть другие варианты?

Papazyan

Я не понимаю, почему ты категорично заявляешь, что таких проблем не возникало бы. В реальной жизни нас никак не спасает тот факт, что мы можем создать экземпляр (абстрактного) класса. Что на счёт читаемости кода? Да и автотестами такие умельцы наверняка покроют последние реализации, с ненужными функциями. Я сомневаюсь, что язык программирования спасёт от нарушения базовых принципов. Но всё-таки мне не хотелось бы об этом спорить именно в этой ветке, потому что это в любом случае будет малоаргументированным холиваром.
Вот в CLOS, например, главный акцент сделан не на классах, а на полиморфных функциях, а классы всего лишь что-то типа структуры с данными. Т.е. даже тупому челу не пришла бы в голову мысль создать СлонВМашине и СлонВПоезде, потому что данных дополнительных, которые бы оправдывали новый класс нет. Он бы сразу знал, что создавать надо полиморфную функцию - moveOnCar, которую можно перегрузить хоть для слона, хоть для мартышки.

Papazyan

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

kokoc88

Вот в CLOS, например, главный акцент сделан не на классах
Вот когда на рынке можно будет найти разработчиков на лиспе, когда он будет работать хотя бы вполовину скорости C# или Java, когда у него будет огромный коммьюнити, когда .... Много чего когда, и в том числе когда я поимею пятилетний опыт разработки на одном из диалектов лиспа, я обязательно напишу сюда пост о том, какие ошибки в проектировании делают рядовые кодеры. И мы все поймём, что язык программирования нас не спасает.

katrin2201

Если я правильно понял, твое предложение заключающееся в "давайте юзать правильные языки ООП":
1. сильно холиварное
2. пенартур-стайлное
ЗЫ Ты существенно недооцениваешь тупых челов.

kokoc88

Твой пример настолько жуткий, что я даже не сразу понял, о чем речь. Т.е. идея завести два класса из слона для передвижения настолько дикая, что сложно представить, как до такого можно додуматься.
Очень хорошо, что до этого додуматься сложно. Зато почему-то просто додуматься до ModelSerializedToFile, ModelSerializedToDb, ModelSerializedToXml. Разница с перевозкой слона очень большая.

katrin2201

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

Papazyan

Зато почему-то просто додуматься до ModelSerializedToFile, ModelSerializedToDb, ModelSerializedToXml. Разница с перевозкой слона очень большая.
Ты бы перевел названия на русский и сразу бы дебилизм стал виден во всей красе. Что такое МодельЗаписаннаяВФайл? Что изменилось в модели после ее записания в файл, что нужно вводить отдельный класс?

katrin2201

Ты бы перевел названия на русский и сразу бы дебилизм стал виден во всей красе. Что такое МодельЗаписаннаяВФайл? Что изменилось в модели после ее записания в файл, что нужно вводить отдельный класс?
У них есть один и тот же метод сериалайз по разному реализованный. Пока все хорошо.

kokoc88

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

kokoc88

то такое имеет место на жизнь
Если бы там было то, что имеет право на жизнь, я бы сейчас не парился. Я чувствую себя как в детском саду, вынужденным объяснять прописные истины.

Papazyan

У них есть один и тот же метод сериалайз по разному реализованный. Пока все хорошо.
А в чем разница реализации? Ведь то, куда сериализуется объект не имеет для него значения. Почему бы не оставить одну универсальную функцию Serialize, а ей передавать абстрактный сериализатор с функциями типа Start, WriteInt, WriteString, End.

kokoc88

Ведь то, куда сериализуется объект не имеет для него значения.
Ты что, один работаешь? Или не с разработчиками? Давно не слышал таких аргументов, как "зачем, зачем нам лишний класс?", "зачем нам всё усложнять?", "сериализация объекта - это вообще его функция, добавляем разную сериализацию через наследование как новые функции класса", "а SRP вообще изобрели в 1979 году, сейчас всё поменялось", "так, как ты предлагаешь, мы будем делать год". Для некоторых людей умные слова не имеют никакого веса, пока не удастся ткнуть носом в конкретную проблему на практике и тогда, может быть, только может быть, тебя послушают.

Papazyan

Да, ужасные проблемы. А ты чего менеджер? Так тогда ты можешь настоять на своем, а если нет, то можно расслабиться и готовиться к неизбежному :grin:

katrin2201

Я тебя очень понимаю, у меня тут у самого сейчас кругом все так, только еще печальней.
Люди пишут по полжизни на PL/SQL и ничего другого не знают. И генерят такие ТЗ, где заново изобретаются велосипедные персистенсы, только на этот раз вчистую сделанные на PL/SQL.
Как сказать им, что они не правы, я не знаю. Меня не поймут, пока я не продемонстрирую работоспособной альтернативы, а на это у меня нету необходимого количества времени.
В общем, мой путь - забить.
Тебе, если уж сильно хочется, могу посоветовать попытаться понять, какие доводы для человека являются железными, и давить на них. Если человек не признается, что для него есть железный довод, то давить опытным путем. Делать рефакторинг, находить и показывать выгоды. Уменьшение каплинга, увеличение скалабилити, реюзабилити на примерах, итд итп. Главное давить нудно и монотонно, чтобы человека тупо переупрямить.

katrin2201

Почему бы не оставить одну универсальную функцию Serialize, а ей передавать абстрактный сериализатор с функциями типа Start, WriteInt, WriteString, End.
Ты говоришь о паттерне Builder.
Грубо говоря, имеет целесообразность, если у тебя есть n вещей которые надо сериалайзить в m типов, и писать n x m кейсов не катит.
А когда у тебя 1 вещь и 3 типа для сериалайза без планируемых расширений, то обсуждаемый паттерн только навернет ненужный слой и сделает код более понятным нормальным проггерам.

kokoc88

хотя в UML вроде нет пунктирных стрелочек залитых на конце
Ну если уж всё равно закидали тему офтопиками... Вообще они там есть, называются Realization.

kokoc88

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

6yrop

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

6yrop

у вас же перед глазами ваш проект, а через форум приводить примеры — это не то.

kokoc88

Давайте попытаемся зареюзать ваш код — хуяус потянулись ненужные связи.
И с чего бы это им быть ненужным? Ну подумаешь, есть у нас All.dll, в ней все классы. Я могу их и тут зареюзать, и там....

6yrop

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

evolet

пока не удастся ткнуть носом в конкретную проблему на практике
мне кажется, что есть 2 варианта
1. убеждать в правильности своего решения своими ссылками на авторитетные источники, но это имхо обычно приводит чему-то похожему на холивары, т.к. имхо по сути является "мерянием пиписек авторитетных источников". Про данный пример я где-то читал простые правила, когда надо надо наследовать, а когда нет, сейчас я уже не помню как они формурируются хорошо, и даже забыл как их называют, но сег вечером, если интересно могу дома посмотреть для отправного пункта, так сказать. Кстати, можно на boost сослаться, там больше на твой вариант похоже.
2. вариант, который ты говорил и который зацитирован в самом начале. И проблемы не обызаны быть уже сейчас, проблема может быть потенциальной, т.е. может случиться то-то и то-то (неприяное) если развитие проекта (модуля) пойдет по такому-то и такому-то пути (вполне вероятному). Имхо при должной эрудиции и опыте всегда можно расписать по полочкам почему лучше сделать так, а не иначе на конкретных, пусть и воображаемых, примерах. Аргументация должна сводиться имхо к тому, что при определенных обстоятельствах придется либо больше переделывать, либо негативно скажется на надежности, либо еще что в этом роде. Что касается ModelSerializedToFile, ModelSerializedToDb, ModelSerializedToXml я бы попытался так (наверное, это не лучший способ):
а) во всех трех методах serialize будет общая часть - логичская часть, т.е. логика сериализации - причем (скорее всего) одна и та же будет в трех разных методах (+ не исключено, что логика=способ сериализации во всех трех случаях один и тот же ДОЛЖЕН быть одинаковым)
б) если, допустим Model меняется и добавляется новый член, то его поддержиать становится необходимым сразу в трех методах (а не в одном) (+ в случае отягчающего обстоятельства в виде того, что сериализация ДОЛЖНА быть одинаковой придется вносить сихронные по логике работы правки в совершенно различно выглядещей код, что повышает вероятность ошибок) - вероятность внесения ошибки возрастает в три раза грубо по количеству кода, новых тестовых примеров должно быть не 1, а 3... (еще надо что-то придумать :) )
ЗЫ вообще как уже писали, если Model одна и низка вероятность того, что она будет (много) переделываться, то можно имхо и так оставить

Dasar

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

bleyman

Нет, там находится десяток методов, которые тянут за собой какие-то классы. Просто не использовать их не получается.
Я думаю, в таком случае тебе следует бить врага его же оружием.
Если класс Elephant знает о существовании классов ElephantOnRails и прочих, значит, было допущено грубейшее нарушение методологии объектно-ориентированного проектирования. За которое можно и уволить, между прочим!
Если в рамках моделируемой сферы возможно существование PlainElephant, которого никуда не возят, но соответствующий класс не был разработан и класс Elephant сам не может быть использован в таком качестве, значит, был допущен грубейший промах в ходе объектно-ориентированного проектирования. За который можно и уволить, кстати!
Если класс Elephant содержит множество протектед полей и методов не использующихся классами типа ElephantOnRails, значит, был грубейше нарушен один из основополагающих принципов объектно-ориентированного проектирования: инкапсуляция, за что вполне можно уволить, если кто не знал. Типа, изменять имплементацию Elephant, не затрагивая ElephantOnRails уже нельзя. Плохо, очень плохо! Причём исправление ситуации (через создание отдельного MovableElephant, закрывающего все внутренности) уже очень близко к тому, чего тебе хочется.
Если существует возможность того, что в какой-то момент появится ещё один аспект слонов - ну, не знаю, WorkingElephant и CircusElephant, и придётся дополнительно делать WorkingElephantOnRails ..., то это вообще пиздец хуёвый объектно-ориентированный дизайн, осквернение заветов Буча, етс, за которое надо не просто увольнять, а отрывать яйца, например.
Если же наоборот, ничего такого нет, то я не понимаю причин твоего возмущения, наследование вполне адекватно решает задачу.

vall

типа мочить слона и создавать нового когда доедет? =)

bleyman

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

karkar

В оригинальном посте пример вообще не годится.
Зачем коллега будет брать всю иерархию классов? Он возьмет почти всю - кроме ElephantOn* (раз ему не нужны типы перевозок) - и отличненько зареюзает.
Кроме того, странное обоснование: наследование - плохо, потому что потянуло зависимости. Любое использование чужих исходников тянет их зависимости, наследование и ООП в целом тут ни при чем. Да, где-то наследование - плохо, но обосновывать это надо иначе.

kokoc88

пусть работает с абстрактным классом Elephant и не парится.
Для этого надо реализовать абстрактные методы перемещения. Получится NotMovingElephantWithMovingMethods. Надо же понимать, что такое наследование приведёт к тому, что через некоторое время класс Elephant можно будет с успехом переименовать в MovingElephant, потому что в наследниках будут выделяться общие методы и поля.

kokoc88

значит, было допущено грубейшее нарушение методологии объектно-ориентированного проектирования
Не надо всё усложнять. Нарушение было, когда сказали, что перемещение слона на машине - это слон. Мне кажется, что это более чем очевидно в данном примере. И, как я отметил в предыдущем посте, всё придёт к "перемещение слона на машине - это перемещение слона", что в общем-то верно, но слона оттуда вытащить практически нереально.

kokoc88

Зачем коллега будет брать всю иерархию классов? Он возьмет почти всю - кроме ElephantOn* (раз ему не нужны типы перевозок) - и отличненько зареюзает.
Тогда ему придётся переписать автотесты и реализовать ненужные методы абстрактного класса. Просто так он ничего не зареюзает.
Любое использование чужих исходников тянет их зависимости, наследование и ООП в целом тут ни при чем.
В данном конкретном примере вместо всей подсистемы перевозки можно было вообще не тянуть никакие зависимости.

Werdna

Я тут подумал, как убедить программиста в том, что наследование — один из фундаментальных механизмов ООП, — нужно использовать с умом?
ООП вообще нахуй не нужно в большинстве проектов.
Только дибилы делают всё объектами.
Оставить комментарий
Имя или ник:
Комментарий: