Pull to refresh

Comments 83

Уже сейчас видно, что код становится не очень-то понятным. Если не знать историю его написания, трудно догадаться, чем morning_eat отличается от morning_eat_play.

Я бы просто остановился на morning_play. То, что там как-то пересекается с едой - это не важно, важно что это утренняя игра, что это игра и всё. Сегодня с едой, а через неделю с засохшими какашками.

И не надо классы добавлять. Нейминг очень важен. Хороший нейминг даст больше профита. Плюс название метода всё равно осталось путающим. Да и вообще, во втором случае будет food, а не eat.

слишком СОЛИДно получилось в итоге, денормализация нужна с момента различия настроения котов, имхо)

На мой взгляд в S-примере весь объектно-ориентированный дизайн развалился. Что несколько противоречит его идее.

Здесь, наверное, разумно было бы выделить функциональные классы. Например, "сохранятель в бд", "кормилец" и т.д. Киса для них будет аргументом без оо-связи, ну либо они будут аргументом для кисы )

Мне кажется или тут запахло Java? Нет, мне этот парфюм не мешает, просто ностальгия, продолжайте...

Мне кажется или тут запахло Java?

Это уход от популярного в прошлом антипаттерна Active Record. Полагаю, что это общая тенденция для всех ООП языков.

Нет темы, которую котики не вытянули бы. Если с любовью)

SRP не говорит о том, что модуль должен делать одну задачу.

Сам Мартин написал об этом в своей книге Clean Architecture:

Of all the SOLID principles, the Single Responsibility Principle (SRP) might be
the least well understood. That’s likely because it has a particularly inappropriate
name. It is too easy for programmers to hear the name and then assume that it
means that every module should do just one thing.

дальше пишет

Make no mistake, there is a principle like that. A function should do one, and
only one, thing. We use that principle when we are refactoring large functions
into smaller functions; we use it at the lowest levels. But it is not one of the
SOLID principles—it is not the SRP.

Я в ООП стиле не пишу, прост зашел поглядеть на котика)

Я в ООП стиле не пишу

А если найду?

Шутки в сторону: идеальной парадигмы нет. Иногда и ООП на что-то годится.

Я понимаю, что примеры максимально упрощенные и утрированные, но в процессе упрощения потерялась основная идея объектно-ориентированного программирования: работа с абстракциями и обобщениями.

И получилось, что класс способен описывать только одного конкретного кота Бориса. Хотя, по идее, должен уметь описывать любого кота.

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

И как следствие из этого, настроение кота Бориса не может меняться. Кот Борис в другом настроении - это другой экземпляр кота Бориса.

Вероятно, это может сбить с толку на этапе, когда как раз ознакомление с принципами солид актуально.

Я бы в классе кота ввел поля String name, Integer age, Enum Color, Enum Breed, Address address, List <Action> actions , к примеру. И уже в классе Action рассматривал бы разные действия питомца. Его, в свою очередь, можно унаследовать от абстрактного класса или интерфейса AnimalActions. И уже в классе Action, если оно, конечно, сильно надо (?) Заводить классы Feed, Play, Ability. А вот saveToDb() конкретно к классу кота вообще не должен иметь никакого отношения.

Ваш кот прекрасен! Стремитесь, чтобы и код был такмм же.

class CatFeeding:
    def morning_eat(self):
        print("Омномном")
    def eat(self):
        print("Не люблю сухой корм")
class CatPlay:
    def morning_eat_play(self):
        print("О конфетки, буду катать их по полу!")
    def play(self):
        print("Тыгыдык")
class CatDatabase:
    def save_to_database(self, cat):
        # Код для сохранения объекта cat в базу данных
        print(f"{cat.name} сохранён в базу данных.")

мои глаза...А ведь все что нужно было вам сделать для демонстрации SRP принципа это:

class CoolBorya(Cat):
    def eat(self):
        print("Омномном")

    def play(self):
        print("Тыгыдык")


class CatRepository:
    def add(cat: Cat):
        ...

А касаемо Допустим, ветеринар сказал Боре есть влажный корм только с утра. Давайте тогда разделим функцию eat на завтраки и остальные приемы пищи - нет никакого смысла создавать отдельные методы с повторяющимся частично кодом под это дело, можно просто в зависимости от времени суток реагировать на разный корм разным образом(ничего страшного в if-ах нет если их максимальное количество продиктовано бизнесом и реальным миром. И они никак не нарушают SRP):

class Time_Of_Day(StrEnum):
    morning = auto()
    afternoon = auto()
    evening = auto()
    night = auto()

time_of_day = Time_Of_Day.morning  # захардкоим чтобы не переусложнять пример


class Food:
    def __init__(self, dish):
        self.dish = dish


class Dry(Food):
    pass


class Wet(Food):
    pass


class CoolBorya(Cat):
    def eat(self, food: Food):
        if time_of_day == Time_Of_Day.morning and isinstance(food, Dry):
            self.hiss()
            return

        print(f"<ест {food}}> омномном")

    def hiss():
        print("шшшшшшш!!")


# чтобы не усложнять пример напишем прямо тут создание и "использование" нашего кота
borya = CoolBorya()

if time_of_day == Time_Of_Day.morning:
    borya.eat(food=Dry(dish="влажный корм"))
else:
   borya.eat(food=Wet(dish="сухой корм"))

При необходимости, в зависимости от бизнес домена(нам нужно запрогать как кот действует сам по себе или это мы за ним ухаживаем) мы можем перенести логику проверки комбинации "корм + время суток" в сам метод eat

(p.s. кстати, ваш метод morning_eat_play в финальном примере про SRP это и есть нарушение SRP)

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

Если для бухгалтерии надо 500 функций - их можно в один класс запихнуть и не будет противоречия с этим принципом.

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

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

про снижение регресса и незапланированного импакта.

Ну как бы, примерно 90% авторов таких статей (по моим субъективным оценкам) вообще не помнят о назначении принципов SOLID (которое изложено во втором предложении скажем статьи википедии). Понимаете? Большая часть авторов, решивших писать про эту тему, не дочитала формулировку, что же такое SOLID, дальше первого предложения.

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

А что для вас в этой формулировке не ясно? Это вполне себе цель. И она не совпадает с просто целью "сделать хорошо".

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

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

Если мы импакт таки изолировали, то как и где легкость расширения сломалась?

А что для вас в этой формулировке не ясно? Это вполне себе цель. И она не совпадает с просто целью "сделать хорошо".

Как цель - да, вполне норм. Но ведь и Мартин и ваша цитата из вики в треде ниже не про цель, а про свойство SOLID. Типа solid вот это все дает. А это не так.

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

Я вот знал, что вы спросите. Представьте себе, что у нас в системе есть два различных актора (к примеру, бугхгалтерия и HR) у которых на текущий момент полностью идентичны требования в некой функциональности. Они могут разойтись в будущем, конечно, но сейчас идентичны. Что тут надо сделать с точки зрения компоновки классов? С точки зрения SRP нужно полностью изолировать функциональность друг от друга, но сейчас она совпадает, так что придется сделать пошлый копипаст. С точки зрения гибкости (и, прости госспади, DRY) общее копировать не нужно, нужно просто его выделить, а различия сделать отдельно. Но тогда два актора будут использовать один общий класс и мы нарушаем SRP. Пример слегка синтетический, но общий конфликт передает.
В общем, если коротко, изоляция импакта зачастую мешает переиспользованию. Невозможность переиспользования ломает гибкость.

Типа solid вот это все дает. А это не так.

В цитате что я привел, Intended - это намерения, а не обещания.

Что тут надо сделать с точки зрения компоновки классов?

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

Я вот знал, что вы спросите. Представьте себе, что у нас в системе есть два различных актора (к примеру, бугхгалтерия и HR) у которых на текущий момент полностью идентичны требования в некой функциональности. Они могут разойтись в будущем, конечно, но сейчас идентичны. Что тут надо сделать с точки зрения компоновки классов?


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

мне так нравятся императивные нотки в вашем комменте))

во-первых, это гипотетический пример, ничего трогать я и не собираюсь)

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

Это принципиальная проблема "переиспользование XOR изоляция изменений". В контексте солида и SRP про нее обычно не вспоминают, зато она часто всплывает в обсуждении микросервисов, например, где обычно считается, что лучше уж дублирование, чем куча общих зависимостей, из-за которых страдает независимость разработки и деплоя.
Либо, если вспомнить метрику "хрупкости" кода и афферентные/эфферентные связи, то всплывет та же самая проблема - переиспользование повышает "хрупкость" кода, так как чем больше входящих связей, тем больше неконтролируемый импакт при изменениях.
То есть это не проблема конкретно солида и SRP. Это именно проблема "переиспользование XOR изоляция изменений". Ну или "переиспользование VS снижение сцепленности", можно и так сказать

мне так нравятся императивные нотки в вашем комменте))

во-первых, это гипотетический пример, ничего трогать я и не собираюсь)


Я тоже не с реальным кодом работаю.

SOLID он о том как двигаясь вперёд ничего не сломать. Солидизировать текущий код смысла нет, кроме как перед следующими изменениями.

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

Наверно в плане солида можно пока их считать одним актором.

Это принципиальная проблема "переиспользование XOR изоляция изменений".

Это действительно проблема. Солид он про дублирование. Меньше ломаешь старого, меньше пересечений с изменениями других людей.

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


Где границу проводить нужно каждый раз решать по месту.

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

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

Ваш тейк абсолютно правильный. Солид не является универсальным решением. Он не будет работать везде.

Это типичный трейдофф. За добавленную солжность получаем полюшки.

использовать солид, пока все легко и от него нет проблем, а когда начнутся - смотреть по ситуации

Не совсем, просто "смотреть по ситуации".

Разделение интерфейсов и Лисков стараюсь соблюдать всегда.

Чем больше я читаю такие статьи, и, что немаловажно, комментарии под ними, тем больше пониманию, что 99% прогеров на самом деле не понимают SOLID. Я тоже :)

А вы прочтите хотя бы статью в википедии. Я серьезно. Вот, смотрите, первый абзац:

In software programmingSOLID is a mnemonic acronym for five design principles intended to make object-oriented designs more understandable, flexible, and maintainable

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

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

простите, влезу, но вот вы поцитировали пример типичного маркетинга про solid. Не делает он код более понятным, вообще ни разу. Более того, если вы попробуете пописать код честно так, как описывал Мартин (с этой его ультимативной композицией), вы, возможно, заметите, что ориентироваться по классам стало наоборот сложнее (по крайней мере многие мои коллеги замечали). И с гибкостью есть нюансы, в некоторых случаях может и вредить.
Все что реально делает solid - изоляция незапланированного импакта. Все. В качестве побочного эффекта в 95% случаев повышается гибкость (просто за счет разделения всего и вся), но не всегда. А поддерживаемость = гибкость + понятность.

Гибкость и сопровождаемость - это декларируемая цель. Принципы эти - они ни разу не гарантируют ничего, это не законы физики. Если вам скажут, что они это гарантируют - это будет брехня. И маркетинг.

И в любом случае применение или неприменение принципов (и выбор того, какой конкретно применить) - за автором. И делать это без применения головы я бы не советовал категорически. А если вы в 95% случаев получаете повышение гибкости - так это более чем хороший результат (особенно - если вы этого реально хотели ;)

 изоляция незапланированного импакта. Все.

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

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

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

на мой взгляд как раз "делать с применением головы" чтобы "не беспокоиться о том, что сломается соседний" и означает - понимать вышеуказанные противоречия, понимать, когда у тебя вылезут эти оставшиеся 5 процентов и понимать, что ты с этим будешь делать и почему.

Так и есть. Солид это карго-культ, он должен быть пересмотрен. Во-первых, за ним стоит один человек и при спорах про солид между любого уровня разработчиками с некоторых пор аргументом становится призыв к авторитету этого человека, а не к каким-то объективным метрикам или научным статьям. Во-вторых, он трактуется всеми по-разному, сложно доказать и показать его реальную необходимость и положительные последствия его применения к реальному коду. Зачастую, код, на котором показываются примеры применения солида, можно оптимизировать более всем привычными конструкциями структурного/процедурного/ООП/функционального программирования.

SOLID это маркетинг. Попытка рассказать важные идеи в виде лёгких фраз. Попытка провалилась, так как простого объяснения нет. (Про то, что термины не удачные, это Дядя Боб сам говорит)

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

Я навидался разного кода. Есть еще люди которые функции по сотни строк пишут. Таким SOLID просто на зайдёт. Он в общем-то о разделении на части, просто на более высоком уровне.

Вы абсолютно правы. У меня сложилось точно такое же мнение.

Наследовать ли маленькую коробку от большой или большую от маленькой? Игрушка, влезающая в маленькую, влезет и в большую. А вот Борис, влезающий во вторую, в первую уже не влезет.

Если основа - вместимость, то надо наследовать большую от маленькой. Маленькая вмещает, а большая ещё лучше вмещает.

Если основа - ограничение вместимости, то маленькую от большой. У большой ограничение, а у маленькой ещё лучше ограничение.

Есть маленькая длинная и маленькая плоская. От какой из них наследоваться?

Не наследовать.

Можно наследовать оба класса от БазовойКоробки, если у них есть общий код.

Да и вообще вопрос нужно ли тут два класса? Одного класса и метода BorisFit может и хватить.

То есть подставить большую коробку вместо маленькой нельзя будет?

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

То есть подставить большую коробку вместо маленькой нельзя будет?

Если вы используете интерфейс то можно.

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

Большая коробка не гарантирует, что котик туда влезет. Паниковать не надо, просто обработать такую ошибку.

Если честно, не очень понятная статья. Напр., в разделе никак не раскрывается, какой смысл иметь подтипы коробки Pet, если и в супертип засовывается Cat или Dog - получается, весь нужный функционал уже есть в супертипе. Выглядит как бессмысленный дизайн, что ставит весь пример под сомнение.

В примере C# как-то лихо перескочили от вариантности параметров к вариантности параметров-типов в обобщённых типах.

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

А c# позволяет управлять вариантностью лишь обобщенных параметров. С остальными у него все плохо.

Ну это точно неграмотный дизайн. Как собака будет грызть косточку, если внутри коробки у неё компайл-тайм тип Pet? Классы не должны наследоваться только для того, чтобы лучше соответствовать тому или иному рантайм-состоянию. Лучше переделать пример, чтобы он больше соответствовал реальным грамотно спроектированным иерархиям классов.

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

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

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

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


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

Я получил коробку для питомца, я засунул в нее питомца. На мой стороне проблем нет. Это у вас будут проблемы от того, что передали мне коробку для собаки, а получили из нее кошку.

Попробую свою мысль написать через псевокод:

class Pet
class Cat(Pet)
class Dog(Pet)

class BoxPet
class BoxDog(BoxPet)
class BoxCat(BoxPet)

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

Наследование тут не отражает бизнес логику и должно быть убранно.

К LSP не имеют, они показывают, что использованный вами для объяснения пример иерархии классов бессмысленный: такой в реальности опытный разработчик не спроектирует. Из-за плохого примера страдает качество объяснения.

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

Чтобы соблюсти LSP на 100% не наследуйте эти классы между собой. Очень много вопросов отпадут.

Если косточка это критический момент для собаки, то наследуя коробку для собаки от коробки для питомцев, вы нарушаете LSP. И глядя на это собака будет скулить.

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

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

Уже с превых строк доверие к этой статье упало.

В далёком 1987 году Барбара Лисков сформулировала принцип разработки имени себя.

Её именем он был назван намного позже.

Давайте рассмотрим функцию, которая принимает на вход коробку для питомца и засовывает в неё какого-то, нам не известного, питомца.

Если мы передадим туда клетку для собак, то почему функция с сигнатурой коробки для питомца, ожидает собаку?

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

<sarcasm on>
Было бы здорово если были бы какие-то рекомендации для программистов, чтобы не делать такого странного наследования.
<sarcasm off>.

Я интерпритирую принцип подстановки Барбары Лисков как: При наследовании не ломайте контракт родительского метода. Проще говоря: если кошка не умеет делать апорт как надо, не наследуйте её от класса у которого есть аппрот.

Контравариантность не вписывается в LSP


Без уточнения, что это generics вообще ничего не понятно. BoxAnimal и Box[Animal] это две разные вещи.

Если вы вместо list[Numbers] передадите list[int] и попробуете сдалать numbers.append(1.0), то в некоторых статически типизированных языках случится проблема. Тут важный момент что list[int] не наследуется от list[Number]. Принцип здесь пообще не применим.

PS. В питоне списки инвариантные, они хранят только один тип данных: ссылку.

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

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

Она не является ответом на ваш вопрос про коробки, если не сложно дайте пожалуйста чёткий ответ, именно на ваш вопрос про большие и маленькие коробки и аргументируйте его. Лучше с примерами [псевдо]кода.

Вы ее даже не почитали. Чтобы понять, как я это понял, вам придется ее все таки прочитать.

Чтобы понят как Вы поняли, я должен спросить Вас. Почитав статью Я пойму как Я её понял.

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

Очень странная статья.

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

Не понятно, к чему тут указана ковариантность. Вариантность относится к тому как комплексные типы выстраиваются в иерархию подтипов. То есть, если у нас есть комплексный тип I, то I[A] <: I[B] если A <: B (ковариантность) или если B <: A (контрвариантность).

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

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

Извиняюсь, но я по душню...

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

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

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

1) Первое что в голову прилетает, зачем вообще этот код, он, ничего не делает. достаточно txt файла..

2) А зачем классы в первом примере ? зачем они вообще, когда все это же достаточно функций или просто txt

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

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

потом объяснение того же single...

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

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

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

Проблема в большинства статей про SOLID в том что они показывают примеры которые никак в этих принципах не нуждаются. Всю логику в этих примерах можно описать в одной функции с кучей if else и их все равно будет просто понять.

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

И еще одна проблема в том что подбираются примеры которые завязаны на физическом мире и у них интуитивно понятная логика, т.е. все прекрасно понимают что коты не могут лететь, а от длины усов не зависит скорость бега. Но в 99% случаев, код не описывает интуитивно понятные физические объекты и действия которые ограничены законами физики. Чаще всего мы работаем и абстрактными объектам которые сами придумали и сами задаем им правила и ограничения. И вот тут уже все сложнее. Это новый воображаемый мир в котором кот может летать но только если его погладить против шерсти три раза в полнолунии с интервалом в одну минуту, но если это сделать в полдень то должен взорвется что-бы он не превратился в дракона. Ну вот такие требования у бизнеса, что поделаешь...

Принцип единственности ответственности (Single Responsibility Principle, SRP) гласит: каждый класс или модуль должен иметь только одну причину для изменения. То есть он должен выполнять только одну задачу.

Простите, но как вы из первого предложения вывели второе?

Причина для изменений - это люди. Вот тут разъяснение этого принципа от самого автора.

Что называется, начали за здравие, закончили за упокой

Очень интересно, но ничего не понятно.

Принцип единственности ответственности (Single Responsibility Principle, SRP) гласит: каждый класс или модуль должен иметь только одну причину для изменения.

Да

То есть он должен выполнять только одну задачу.

Нет

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

Принцип single responsibility явно нарушен судя по названию :-)

def morning_eat_play(self)

Самое сложное в SOLID - это вспомнить расшифровку аббревиатуры.

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

Тоже не понимаю. Код ужасен, а принципы не поняты самим автором.

автор гений. теперь я знаю как объяснять SOLID на собесах

Допустим, ветеринар сказал Боре есть влажный корм только с утра.

Боря только слова эти пропустил мимо ушей. Единственная ответственность Бори это кушать.

А что и когда, это будет решать его FoodProvider. Что даст ему то даст.

У FoodProvider есть два заказчика Mommy и ветиринар. И требования у них пересекаются. Вот тут-то самое время накидать классы.

Может сработать такой пайплайн из трёх частей:

available_food = Fridge().get_all_food_at_home()
allowed_food = Veterinar().filter_healthy_food(available_food )
cat_food = Mommy().give_yammy(allowed_food ) if available_food else Mommy().run_for_food(Veterinar().get_recommendations()

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

Стоит ли это гибкость той дополнительной сложность? Этот вопрос заказчику.

Возник вопрос по этому коду и последней части:

class BoryaCoolCat:
    def __init__(self, sound: Sound):
        self.sound = sound
    def speak(self):
        return self.sound.make_sound()

А мы тут никак не должны CatSound вызывать или ссылаться? Типа self. sound.CatSound.make_sound() или вызвать в init...

Нет, предполагается ссылаться на CatSound в коде который будет создавать / использовать объекты:

borya_sound = CatSound()
borya = BoryaCoolCat(borya_sound)

CatSound is Sound (наследование), поэтому тип self.sound может быть CatSound, а может быть любой другой наследник Sound (DogSound, UFOSound), абзац из статьи:

Заметили самый сок? В конструкторе класса мы получаем звук типа Sound. Значит, если мы в какой-то момент решим, что Боря не кот, а, например, крокодил, нам не нужно будет переписывать класс BoryaCoolCat. Достаточно просто передать в него любой другой класс, который наследуется от Sound!

Нет, предполагается ссылаться на CatSound в коде который будет создавать / использовать объекты:


В классе написано, что принимает sound: Sound. Значит с точки зрения автора этого класса Боря мяукающий крокодилом - это нормльно. И вся ответственность за это передана на вызывающего.

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

По первому принципу (srp). Получили в итоге три класса: СatFeeding, CatPlay, CatDatabase Объясните, пожалуйста, кто разобрался, как далее мне с ними работать: создавать объекты (например, несколько разных котов: Борис и Морис) и пользовться методами реализованными, атрибуты типа имени, цвета и тд в каком из классов реализовать?

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

Sign up to leave a comment.