Тренды, искусственный интеллект, виртуальная реальность, робототехника, разработка — SE2017

Принцип подстановки Барбары Лисков

Продолжая серию «ООП — это просто», на этот раз я попытаюсь рассказать о принципе подстановки Барбары Лисков (Liskov substitution principle, далее LSP). Поскольку я считаю этот принцип венцом SOLID, то чтобы читать эту статью, нужно ясно понимать, что такое уровни абстракции и DIP. Попутно я расскажу в меру своего понимания о принципе открытости/закрытости (open/closed principle, далее OCP).

Если потратить некоторое количество времени и положить на алтарь науки пару десятков нервных клеток, можно найти такие самые простые определения данного принципа:
— «Функции, которые используют базовый тип, должны иметь возможность использовать подтипы базового типа, не зная об этом».

Или так:
— «Наследующий класс должен дополнять, а не замещать поведение базового класса».

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

Как не надо

Разжевывать суть принципа будем на примере вот отсюда (C++). Поскольку моим основным языком программирования всё ещё является PHP (haters gonna hate), то и пример будет адаптирован под стилистику этого языка. Наберитесь терпения, пример длинный. В нём мы нарушим принцип подстановки Барбары Лисков и посмотрим, к чему это приведёт.

Задача следующая: есть два похожих термостата (нагревателя) от разных производителей: BrandA и BrandB. Это очень простые приборы. Всё, что они умеют — это греть воду по команде и измерять текущую температуру воды. Никакими другими супер-способностями создатели их не наградили.

Нас просят запрограммировать единый пульт управления для 100500 бойлеров, оснащенных данными термостатами.

Не долго думая, пишем нечто по следующему клише:

abstract class Boiler {
   private $desirableTemperature;
   public function setDesirableTemperature($temp) {
       $this->desirableTemperature = $temp;
   }
   public function getDesirableTemperature() {
       return $this->desirableTemperature;
   }
   abstract function initializeDevice();
   abstract function getWaterTemperature();
   abstract function heatWater();
}

class BrandABoiler extends Boiler {
   function initializeDevice() { /*use API BrandA*/ }
   function getWaterTemperature() { /*use API BrandA*/ }
   function heatWater() { /*use API BrandA*/ }
}

Класс для BrandB пишем по аналогии с BrandA, с той лишь разницей, что ласкаем его по API BrandB.

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

$myBoiler = SomeFactory::getNextBoiler();
$myBoiler->setDesirableTemperature(37);
$myBoiler->initializeDevice();
while($myBoiler->getWaterTemperature() < $myBoiler->getDesirableTemperature()) {
   $myBoiler->heatWater();
}

Утрированно получаем workflow:

Работа сделана. Радуемся, считаем деньги.

Спустя какое-то время, приходит наш заказчик и с порога выдыхает: «Воооть!»

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

Обреченно вздыхаем, лезем в код, ваяем класс нового бойлера:

class BrandCBoiler extends Boiler {
   function setDesirableTemperature($temp) {
       /*устанавливаем желаемую температуру воды сразу в бойлере BrandC*/
   }
   function getDesirableTemperature() {
       /*получаем желаемую температуру воды напрямую из бойлера BrandC*/
   }
   function initializeDevice() { /*use API BrandC*/ }
   function getWaterTemperature() { /*use API BrandC*/ }
   function heatWater() { /*empty*/ }
}

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

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

Произошла подмена поведения. Вот этого:

abstract class Boiler {
   private $desirableTemperature;
   public function setDesirableTemperature($temp) {
       $this->desirableTemperature = $temp;
   }
   public function getDesirableTemperature() {
       return $this->desirableTemperature;
   }
   //bla-bla...
}

На вот это поведение:

class BrandCBoiler extends Boiler {
   function setDesirableTemperature($temp) {
       /*устанавливаем желаемую температуру воды сразу в бойлере BrandC*/
   }
   function getDesirableTemperature() {
       /*получаем желаемую температуру воды напрямую из бойлера BrandC*/
   }
   //bla-bla...
}

Суперкласс Boiler проектировался с таким расчетом, что нужная температура воды инкапсулируется внутри его свойства $desirableTemperature, и затем это значение может быть извлечено и использовано. Мы же, наплевав на эту задумку, пытаемся «срезать путь», подменив в субклассе методы, содержащие фундаментальное проектное поведение. То есть теперь setDesirableTemperature() перестал записывать температуру в контейнер $desirableTemperature, а getDesirableTemperature() перестал оттуда читать.

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

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

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

Если развить мысль и предположить, что наш автомобиль установлен в клозете какого-то крафтового кафе и задумывался как «крутое дизайнерское решение интерьера уборной», то замена двигателя автомобиля на унитаз — принципа подстановки Барбары Лисков не нарушит.

Как надо

Так что же нужно было сделать, чтобы корректно использовать бойлер BrandC в рамках существующей архитектуры? Самые находчивые проектировщики повскакивали с мест и перебили: «А не надо так использовать наши классы! Надо исправить клиентский код. Чего вы его так пишете! Вот можно в нём поменять местами строки с вызовом setDesirableTemperature() и InitializeDevice() - и всё заработает».

Но разве кому-то из нас непривычна ситуация, что код классов пишет один программист, а клиентский код к ним — совершенно другой (и в другое время). Где гарантии, что ваши классы будут использоваться только так, как вы того хотите? Надо проектировать с расчетом, что клиентский код к ним будет ваять идиот — не промахнётесь. К слову, тут может быть отсылка к Инкапсуляции (en capsula: взятие в капсулу) — нужно надёжно защищать свои механизмы от внешних воздействий (в том числе от кривых рук).

А кто-то может предложить, например, такое: «Давайте перепишем initializeDevice() в абстракции Boiler, чтобы при инициализации субкласса сразу задавать требуемую температуру». — «Конечно-конечно, — ответим мы. — Только ты забыл про принцип открытости/закрытости», — Open/closed principle, далее OCP.

Определение звучит так: «Классы должны быть открыты для расширения, но закрыты для изменения».

Что это значит: не трогайте стабильно работающие классы, не переписывайте их, не дописывайте. Субклассируйте, и уже там вносите новое поведение. Почему так? Потому что старый код надёжен, он двести раз протестирован и проверен в боевых условиях — он работает, и не надо его ломать (100500 бойлеров BrandA/B стабильно работают). Плюс субклассировать и переписывать — две совершенно разные задачи по объёму и по времени.

Здесь выплывает два вопроса:

1) А как же ошибки? Как мы можем исправлять баги в закрытом для изменений классе?

Ответ: баги надо исправлять. А принцип OCP относится к проектированию, а не к багфиксу.

2) А как же рефакторинг? Разве можно проводить рефакторинг в классе, который нельзя изменять?

Ответ: проводите рефакторинг. Однако это мероприятие влечет за собой предварительное создание «плана рефакторинга» с прогнозированием «боков» грядущей перестройки. Рефакторинг — это перепроектирование, переделка, новый архитектурный цикл. Тогда как OCP — это ни какое не «пере». Это принцип, призывающий не пороть горячки. Всё равно, как вы бы у себя дома перекладывали проводку всякий раз, когда вам нужно передвинуть торшер.

Таким образом, создать BrandCBoiler, не нарушая принцип подстановки Барбары Лисков и учитывая принцип открытости/закрытости, можно, например, так:

class BrandCBoiler extends Boiler {
   function initializeDevice() {
       //через API BrandC уговариваем устройство поработать
       //передаём по API нужную температуру из $this->getDesirableTemperature()
   }
   function getWaterTemperature() { /*use API BrandC*/ }
   function heatWater() { /*empty*/ }
}

Мы субклассировали Boiler в BrandCBoiler (учли OCP) без подмены поведения базового класса (учли LSP).

Итак, мы видим, что принцип подстановки Барбары Лисков — логический. Мы не сможем проверить его нарушение никакими IDE, синтаксическими анализаторами и т.п. (ну разве что тесты в помощь, если знаете, где копать). Пользоваться DIP способен только человеческий мозг, развивший в себе способности к абстрагированию и философии. Во всяком случае, пока что. Но это уже другая история.

Послесловие

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

Всем желающим продолжения рекомендую прочесть книгу Эрика и Элизабет Фримен «Паттерны проектирования» — свои статьи я написала как, по моему субъективному мнению, «недостающие части» к этой прекрасной книге. Я попыталась сохранить её стилистику и ясность изложения. Если вам понравились мои истории — эта книга вам тоже придётся по вкусу.


P.S. Вы ни разу не видели боевых проектов из четырёх классов, основная функциональность которых представлена комментариями? Я тоже. Поэтому, прежде чем писать пост, пожалуйста, задумайтесь, для какой цели были созданы эти примеры. Материал подготовлен мной с учётом моих вкусов, видений и моего персонального чувства прекрасного — он отражает образ моей мысли и может отличаться, как от истины, так и от мнения и видений других людей.

LinkedIn

Лучшие комментарии пропустить

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

Советы читателям:
1. Если что-то непонятно — попробуйте вдумчиво перечитать статью несколько раз прежде чем задавать вопросы автору. Не такая она и большая, за пятнадцать минут осилить реально. Если тема по-настоящему интересует, пятнадцать минут — не такая уж большая жертва
2. Если не ясно почему авторесса тот или иной пример использует — попробуйте придумать пример, который, на ваш взгляд, лучше подойдёт и будет настолько же лаконичным. Я попробовал к данной статье придумать — у меня не получилось.
3. На мой взгляд, много всяких негодующих комментариев адресуются на самом деле области, которая в статьях описывается, а не автору статьи. Область такая тонкая, в ней много субъективного, много чего на интуиции строится... И это замечательно, ведь архитектура ПО — это то немногое, что осталось в программировании от искусства. Живая философия.

Совет авторессе:
1. Знаю, будете обижаться, но всё-таки напишу: спокойнее реагируйте не критику. Люди, как правило, когда комментируют, не желают зла. Они реально могут не понимать каких-нибудь вещей или могут что-нибудь понимать по-своему (не факт что правильно, ага). Редко у кого есть желание унизить вас или возвыситься за ваш счёт. Главная же фишка критики заключается в том, что если с критиками общаться спокойно, то можно расширить свой кругозор, или по крайней мере, научиться понимать почему люди видят мир так или иначе. Проверял на себе — в результате дискуссии обретается зрелый взгляд на многие вещи.
В конце концов, даже если кто-то желает вас унизить в комментариях — ваш гневный комментарий принесёт лишь радость троллю. Зачем тратить нервы лишний раз? Лучше просто игнорировать.

Под конец хочу ещё раз поблагодарить Наталью за труд!.. Как там было в первой статье, «пишите ещё»!

Редакция DOU прислала мне в знак признательности чашку со своим лого! Урааа! Теперь у меня есть оригинальная чашка DOU! Так что, господа критиканы — можете подавиться ) А я пойду попью чайку из своего нового сокровища. Хе-хе-хе.

226 комментариев

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

Возможно это будет получше пример:
— есть всем известный интерфейс ActiveRecord (crud для работы с записью из хранилища)
— StorageConnection (соединение с хранилищем, вызывается «из» ActiveRecord)
— MysqlStorageConnection (предок от DBConnection).
Так вот идеальный вариант, когда мы MysqlDbConnection заменили на MongoDbConnection, чтобы брать данные из другого хранилища и код при этом продолжил работать без изменений.

Не хочется никого обижать, но все же автору стоит изучить матчасть того, о чем он пишет. Все же одно дело проконсультировать коллегу, а совсем другое отдавать свое видение вопроса на суд N-го количества читателей.
---
Принцип подстановки реализуется с помощью контрактов (т.н. контрактное программирование). В частности, в PHP, да и много еще где это Интерфейсы. Ни о каком наследовании речи не идет. Никаких абстрактных классов. В вашем примере должны существовать по сути три сущности — класс Пульта, контракт (интерфейс) Бойлера и любое кол-во классов Бойлера, которые реализуют интерфейс. В интерфейсе представлены все нужные вам методы, включай геттер желаемой температуры (который стал у вас камнем преткновения). Как конечный класс Бойлера их реализует — его дело, главное чтоб были реализованы. И естественно должен присутствовать тайп-хинт интерфейса Бойлера в методе Пульта
---
Если рассматривать эту реализацию как отдельную либу на том же пекеджисте, то данная реализация позволит юзерам пекеджа не зависеть от любых абстрактных классов (и их свойств), не тянуть их годами из проекта в проект. Хочешь добавить новый бойлер? напиши класс и выполни контракт — Пульт сделает все что нужно. Такой подход не позволит «нарушить принципы проектирования» при любом желании

Наталия, Вы пишите прекрасные материалы, спасибо за Вашу работу. Если будет что-то еще, то это здорово :)

На мой взгляд, одного лишь ООП мало для правильного понимания изложенной концепции, и любой разговор о LSP будет неполным без объяснения на примере функциональных типов данных. Из всего, что видел, наиболее понятно и доходчиво по теме высказался широко известный в узких кругах Mike Ash.

www.mikeash.com/...e-and-contravariance.html

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

Редакция DOU прислала мне в знак признательности чашку со своим лого! Урааа! Теперь у меня есть оригинальная чашка DOU! Так что, господа критиканы — можете подавиться ) А я пойду попью чайку из своего нового сокровища. Хе-хе-хе.

Так что, господа критиканы — можете подавиться )
Ничего так, культурненько.

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

А мне вообще ни**** не обещали за 4000 сообщений :(

В linkedin не забудьте добавить, в раздел «награды» )

В линкедине у меня ссылки на мои статьи в разделе «публикации». С чашкой, наверное, на аватар сфоткаюсь.

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

Дамы и господа, а знаете в чём основная трудность написания сложных технических статей с последующей публикацией их на DOU? Я только что почитал каменты неожиданно понял. Это занятие подобно сексу на центральной площади города в летний праздничный день. Не успеешь начать — советами замучают. :D
А если серьёзно, добрее нам нужно быть друг к другу и более терпимыми. Чтоб суметь ближнего хоть в чём-то наDOUмить, нужно прежде всего, относиться к собеседнику со всем возможным уважением, а к предмету обсуждения — максимально вдумчиво. Чего я нам всем искренне желаю. А автору цикла статей — большое спасибо за интересный и полезный материал и всяческих всевозможных успехов.

ЗЫ Да, я зануда. Да, я ещё и дамский угодник. :) И таким себе нравлюсь. ;)

Неплохое описание. Особенно порадовали картинки. Но есть несколько но. Начальная модель бойлера анемична и ни капли не заботится о клиентском коде. Если эту модель воплотить в реальный бойлер то получится следующее. Интерфейс реального бойлера:
* есть бойлер, у кторого присутствует два индикатора — желаемая температура и реальная температура
* есть пульт ввода значения желаемой температуры
* есть кнопка, при нажатии на которую будет нагреваться температура воды в бойлере, как только вы отпустите эту кнопку — нагрев прекратится
Теперь рассмотрим один из сцеариев использования — помыться в душе при комнатной тмпературе 10:
1. хозяин подходит к бойлеру
2. хозяин вводит желаемую температуру 30
3. бойлер отображает эту температуру на индикаторе желаемой температуры
4. хозяин нажимает на кнопку нагрева и держет ее пока идикатор реальной температуры не достигнет 30
5. хозяин включает душ и начинает мыться
6. реальная температура воды в бойлере падает до 25
7. хозяин выключает душ и подходит к бойлеру
8. хозяин держит кнопку нагрева до тех пор пока температура не достигнет желаемой
9. хозяин повторяет пункты 4-8 до тех пор, пока не станет чистым
Так к чему все это — бездумное применение SOLID’а особо не поможет и не повысить качество пректа. И да, иногда лучше поменять проводку, чем постоянно лазить на потолок, что бы включить/выключить торшер в другой комнате.

Интерфейс реального бойлера
Интерфейс реального бойлера аппаратный, всё остальное реальному бойлеру не нужно. Температура устанавливается при монтаже один раз, например, 75 градусов и забывается о его существовании лет на 5, до следующей проверки газового оборудования.

Да, в таком случаи пункт 2 можно выкинуть из сценария, но все остальное прийдется проделывать каждый раз :(

Да, но в статье приводятся слабые аргументы необходимости вашего решения. Например:

Но разве кому-то из нас непривычна ситуация, что код классов пишет один программист, а клиентский код к ним — совершенно другой (и в другое время). Где гарантии, что ваши классы будут использоваться только так, как вы того хотите? Надо проектировать с расчетом, что клиентский код к ним будет ваять идиот — не промахнётесь. К слову, тут может быть отсылка к Инкапсуляции (en capsula: взятие в капсулу) — нужно надёжно защищать свои механизмы от внешних воздействий (в том числе от кривых рук).

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

«А не надо так использовать наши классы! Надо исправить клиентский код. Чего вы его так пишете! Вот можно в нём поменять местами строки с вызовом setDesirableTemperature() и InitializeDevice() - и всё заработает».

Или

Ответ: проводите рефакторинг. Однако это мероприятие влечет за собой предварительное создание «плана рефакторинга» с прогнозированием «боков» грядущей перестройки. Рефакторинг — это перепроектирование, переделка, новый архитектурный цикл. Тогда как OCP — это ни какое не «пере». Это принцип, призывающий не пороть горячки. Всё равно, как вы бы у себя дома перекладывали проводку всякий раз, когда вам нужно передвинуть торшер.

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

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

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

Ох, везло вам, видимо, с проектами, если вам не знакомо такое развлечение как «говнокодим всем офисом под дедлайн»

надо уже, наверное, FAQ создавать для ответов на самые популярные комменты.

А можно ещё посмотреть на проблему так: интерфейс сущности это не только набор допустимых с ней *операций* (в терминах примера — список методов бойлера, которые вызывает клиент), но еще и множество *допустимых комбинаций* этих операций (в терминах примера, у старых бойлеров допустима последовательность вызовов (setDesirableTemperature, initializeDevice, heatWater), а у BoilerC — нет.
Очень может быть, что для какой-нибудь экваториальной местности в эту систему добавят покрашенные черной краской бочки, греть которые будет вообще солнце без всяких там драйверов. То есть поведение ну совсем заменят. Вызов метода setDesirableTemperature будет, скажем, приносить в жертву пропорциональное число девственниц, а initializeDevice будет поливать их кровью бочки. И если это не противоречит местным ритуалам, система по-прежнему будет замечательно работать, вода будет греться.

но еще и множество *допустимых комбинаций* этих операций

Это всё является частью контракта класса. (И формулировка LSP через контракт — чище и яснее.)

Если проектировщик/разработчик рассматривает интерфейс объекта/класса как

множество *допустимых комбинаций*
, то скорей всего нужно задуматься о переходе к процедурному программированию — будет проще и понятней и не неужно будет вникать в SOLID’ы и другие, более сложные, понятия ООП.

Но так он никогда не прочтет Библии или Корана!

Это становится интересным :) Пусть я вызвал у файла close(), а затем seek() и совершенно логично получил по голове за это (неважно, в виде exception, кода возврата или ещё как-то). Как мог SOLID это предотвратить?

А почему вы считаете, что в классе File должны присутствовать методы close() и seek()? Если класс File содержит инфомацию о файле, то почему он должен позволять читать содержимое файла? Читаем внимательно SRP (Single Responsibility Principle) и понимаем, что в данном классе аж целых 2 отвественности, поэтому разделяем ответсвзенность чтения в класс Stream, в класс File добавляем метод def open(): Stream и вопрос закрыт.

Если класс File содержит инфомацию о файле

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

Читаем внимательно SRP (Single Responsibility Principle) и понимаем, что в данном классе аж целых 2 отвественности

На каком основании сделан такой вывод?

в класс File добавляем метод def open(): Stream и вопрос закрыт.

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

На каком основании сделан такой вывод?

Так описывайте весь контекст. Из того что есть, более чем «правильный» вывод.

Вопрос не закрыт

Да, вы правы. В классе Stream могут присутсвовать методы seek() и close() и еще до дестятка методов. Но рассматривать различные множества допустимых опираций не упростит понимание использования объекта. А вот если рассмотреть множество допустимых состояний объекта, то все станет на свои места.

Вот разрежьте мне, пожалуйста, это по SRP.

Вы мне еще предложите разрезать вот этот класс msdn.microsoft.com/...-us/library/1xb05f0h.aspx . С такими предложениями обращайтесь к авторам.

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

Вы мне еще предложите разрезать вот этот класс

Почему нет? Была бы отличная иллюстрация.

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

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

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

У меня большое подозрение, что и «парадигмы ООП» в каждои из миров существенно разные. Иначе сложно объяснить столь разные подходы и позиции.

Так и есть. Работая, например, в геймдеве, с идиотизмом нарративом вида «делаем всё по паттернам из GoF» практически не сталкивался. Что не мешает при надобности все эти паттерны применять — и не только эти, а ещё и языковые. Просто акцент вообще не на этом делается при обсуждении разработки.

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

Так ведь хреначенье по шаблонам приводит к экономии времени не на разработке, а разве что на «пять минут на подумать». Кода с ним приходится как раз писать больше и разбираться в нём сложнее.

Ниже приводится ссылка dou.ua/...principle/#comment_921895 на хороший цикл статей об ООП, советую прочесть все.

Я уже прочитал десяток верхних. «Хороший» это цикл только в том плане, что надо не думать, как автор, и не следовать его советам.
Причём, писать в таком стиле мог бы каждый, у кого есть достаточно желчи: например, вслед за такими граблями написать грозную заметку типа «++ is evil», а за такими — «Don’t use negative offsets. Ever.» Столько же понтов и столь же бессмысленно в итоге.

В примерах хотелось бы видеть как именно сломался код, а не абстрактное

/*устанавливаем желаемую температуру воды сразу в бойлере BrandC*/
, так как нельзя быть уверенным что метод действительно сломан, а вдруг там в конце return parent::setDesirableTemperature($temp);

Где то «там» и куча статей по SOLID. Может тогда проще не писать?

статью прочтите для начала, потом комментарии пишите.

Статью я прочитал несколько раз, и по этому написал.
А вот почитав статью на С++ я понял, что к чему) Дело в том, что там у автора нормальный комментарий в коде:


// Нафиг ненужный метод: мы температурой управляем в другом месте
А ваш комент похож на то, что там скрыта логика (привет всяким //bla-bla...). Можно было просто написать return; или return $this; Или лучше перефразировать оригинал но так, что бы читатель понял что код удален. Об этом я и писал,что не понятно что происходит. А вы грубо посылаете людей в оригинал, не хорошо...
Разжевывать суть принципа будем на примере вот отсюда (C++).
Забавно, что они тоже взяли пример с другого ресурса.
Задача следующая: есть два похожих термостата (нагревателя) от разных производителей: BrandA и BrandB.
и далее
Перед нами напичканный электроникой бойлер BrandC, который получает указания о желаемой температуре воды один раз и сам её поддерживает.
В начале мы управляем нагревателем на основе показаний термометра, а тут мы только задаем температуру пытаясь если взять за аналогию Уровни модели OSI применить код из скажем транспортного уровня прикрутить на прикладной. В итоге подав на вход не то, что разработчик ожидал получить имеем нечто вроде кирпича в стиралке. Я к тому, что пример не очень удачный.
Итак, мы видим, что принцип подстановки Барбары Лисков — логический. Мы не сможем проверить его нарушение никакими IDE, синтаксическими анализаторами
Переопределенные не прайват/протектед методы мне кажется вполне могут быть найдены статическим анализом.

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

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

Если не получается придумать, то может лучше использовать классику? Тут igor.quatrocode.com/2008/09/solid-top-5.html очень маленький и емкий пример нарушение принципа Барбары Лисков.

Так и думала, что пример с прямоугольником и квадратом. Честно говоря, когда я в своё время разбиралась с LSP, мне этот пример был непонятен «ну ваще». Но если вам нравится — используйте. Дело вкуса.

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

Прятаться от критики за P.S. не совсем обычная практика для обучающей статьи.
Мол, если я где ошиблась/накосячила — то это неважно, я же предупредила в P.S.

а у вас есть конкретне вопросы по материалу статьи или просто покритиковать хочется? Если есть вопросы — давайте я попробую ответить.

Вы сами создали проблему, сами же ее героически решили, почти.

Во-первых, как правильно внизу написали, вы выставляете кишки бойлера наружу. Что такое бойлер с точки зрения клиента? Это что-то, что умеет держать воду заданной температуры, всё! Клиента не волнует что его надо как-то иницилизировать, что его надо подогревать в зависимости от текущей температуры. А что если температура уже больше заданной? Добавите бойлеру метод coolWater? Это никуда не годиться. Это все внутреннее устройство бойлера, которое клиента не волнует никак. Клиент вообще должен работать с простым интерфейсом:

interface IBoiler
{
    void SetTemparature(int temparature);
}

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

EDT Сорри, только сейчас увидел P.S. секцию )

О том и речь — сначала прочтите, потом пишите гневные отзывы.

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

Как я понял, в этом и заключается принцип подстановки Барбары Лисков — лепить так, чтобы не наломать старую логику взаимодействия.

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

оно более простое для тех, кто понимает о чём речь. Попробуйте объяснить это какому-то джуну. Я пробовала.

Забавно, реализуем API с кишками наружу:

while($myBoiler->getWaterTemperature() < $myBoiler->getDesirableTemperature()) {
   $myBoiler->heatWater();
}
то есть рассказываем клиенту о том как бойлер на самом деле греет воду, и предлагаем клиенту следить за этим процессом самостоятельно. То есть, блин, это проблемы с банальной инкапсуляцией.

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

 $myBoiler->setDesirableTemperature($temp)
 $myBoiler->heatWater();
или даже просто:
$myBoiler->heatWater($temp);
все, никаких проблем.

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

Так как раз SOLID — это прежде всего на тему «как надо спроектировать», а не «как надо подбивать костыли и подпорки». Когда надо подбивать костыли и подпорки, SOLID уже проиграл.

Вы читали статью? Признайтесь честно.

Если вы уже знаете SOLID — это очень хорошо. Но если ещё нет — как вы человеку обьясните LSP, не нарушив его?

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

Ok, в рамках Вашей аналогии. Допустим у нас есть интерфейс:

$myBoiler->setDesirableTemperature($temp);
$myBoiler->setDesirableAmountOfWater($amount);
$myBoiler->heatWater();
То есть подходите вы к бойлеру, и крутилками на нем говорите: на какую температуру подогреть воду, сколько воды греть, а потом жмакаете большую красную кнопку «подогреть». Причем, возможен и обратный вариант использования:
$myBoiler->setDesirableAmountOfWater($amount);
$myBoiler->setDesirableTemperature($temp);
нам ведь по сути пофигу в каком порядке крутить крутилки, если большая красная кнопка не нажата.

И тут мы получаем ваш же С-нагреватель, у которого собственно большой красной кнопки нет, ее роль выполняет крутилка «setTemperature» — то есть как только у этой крутилки выставляем температуру — он начинает греть.

Далее делаем ровно тоже самое что сделали вы: для С-нагревателя heatWater — пустой, а setDesirableTemperature — фактически греет воду. По-сути нарушая контракт «установка значения просто устанавливает значение, но не греет воду». И получаем сюрприз: в ряде случаев все хорошо, а в ряде случаев ба-бах (воды еще нет, а мы ее уже греем).

Собственно вот вам и нарушение принципа: heatWater — не работает, а setDesirableTemperature греет воду. То есть подтип не может быть использован без знаний о его особенностях. Пофиксить тоже просто: setDesirableTemperature — должно устанавливать не крутилку, а просто сохранять внутреннее значение, а heatWater — устанавливать крутилку — то есть начинать греть. Тогда внешние контракты будут соблюдаться.

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

Советы читателям:
1. Если что-то непонятно — попробуйте вдумчиво перечитать статью несколько раз прежде чем задавать вопросы автору. Не такая она и большая, за пятнадцать минут осилить реально. Если тема по-настоящему интересует, пятнадцать минут — не такая уж большая жертва
2. Если не ясно почему авторесса тот или иной пример использует — попробуйте придумать пример, который, на ваш взгляд, лучше подойдёт и будет настолько же лаконичным. Я попробовал к данной статье придумать — у меня не получилось.
3. На мой взгляд, много всяких негодующих комментариев адресуются на самом деле области, которая в статьях описывается, а не автору статьи. Область такая тонкая, в ней много субъективного, много чего на интуиции строится... И это замечательно, ведь архитектура ПО — это то немногое, что осталось в программировании от искусства. Живая философия.

Совет авторессе:
1. Знаю, будете обижаться, но всё-таки напишу: спокойнее реагируйте не критику. Люди, как правило, когда комментируют, не желают зла. Они реально могут не понимать каких-нибудь вещей или могут что-нибудь понимать по-своему (не факт что правильно, ага). Редко у кого есть желание унизить вас или возвыситься за ваш счёт. Главная же фишка критики заключается в том, что если с критиками общаться спокойно, то можно расширить свой кругозор, или по крайней мере, научиться понимать почему люди видят мир так или иначе. Проверял на себе — в результате дискуссии обретается зрелый взгляд на многие вещи.
В конце концов, даже если кто-то желает вас унизить в комментариях — ваш гневный комментарий принесёт лишь радость троллю. Зачем тратить нервы лишний раз? Лучше просто игнорировать.

Под конец хочу ещё раз поблагодарить Наталью за труд!.. Как там было в первой статье, «пишите ещё»!

Новая роль несколько жмёт. Не привыкла.

За отзыв спасибо, не ожидала чего-то подобного.

Натали, желаю в след. статье избегать типа смешных «вставок». Вот эти вот «Работа сделана. Радуемся, считаем деньги.», «...ласкаем его по API BrandB», «Пользоваться DIP способен только человеческий мозг, развивший в себе способности к абстрагированию и философии», «...и положить на алтарь науки пару десятков нервных клеток» и пр. — это все мусор.

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

Вова, хороший юмор — это хороший юмор, а остальное — это мусор :)

Ну вот трудновато критерий понять. Как отличить мусор от хорошего юмора? Я сам пытаюсь в статьях хохмить и не всегда получается, судя по реакции людей... Может, вы подскажете?

Мне самому трудно критерий хорошего юмора вкратце описать ) Хорошая шутка — это шутка, сказанная в правильное время и в правильном месте ) А в ИТ с юмором очень грустно. «Впихивание» интернет-мемов и всяких заезженных выражений в статейки ради шутки — это не юмор. А вообще, тонкий и качественный юмор — это книги Жванецкого, это «12 стульев» и «золотой теленок», например ) просто учебники юмора, я бы сказал.

я вот не совсем понимаю, почему вы считаете шуткой фазу

Пользоваться DIP способен только человеческий мозг, развивший в себе способности к абстрагированию и философии

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

аааа, так это не шутка была, а серьезное высказывание??)))

два в одном. Знаете, так иногда бывает, особенно если человек с фантазией.
Вообще-то там был отсыл к ИИ и прочей кибернетической шелухе, которая ещё не умеет строить абстракции. Хотя... кому я объясняю.

А какое отношение имеет философия к абстракции в рамках программирования?)

dou.ua/...les/level-of-abstraction
Читаем статью. Затем открываем вики и читаем там, что Философия — это:

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

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

Натали, лучше бы не писала этот бред

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

По имени-отчеству изволите Вас величать, дабы не ранить утончённую абстрактно-философскую натуру?

Наталия, ваши ожидания не оправдались. Не все homo sapiens способны к абстракции.

Посмотрела фейсбук персонажа: бухать меньше надо.

наоборот, подобные вставки сильно оживляют повествование.
помнится в юности я изучал язык Си по книжке со смешными картинками на полях. Мы ее так и называли — «веселые картинки». Си уже не помню. а книжка запомнилась :-)

а должно было быть наоборот )))))

До речі наполегливо рекомендую вам почитати статті Єгора про ООП — www.yegor256.com/tag/oop.html
Якби він тут написав пост то під ним було би в 10 разів більше коментарів «критиканів» проте його «стиль» так би мовити, має під собою хороше підгрунтя.

проте його “стиль” так би мовити, має під собою хороше підгрунтя.

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

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

а вот иметь более одной ответсвенности — почти сразу плохо

Есть реальные примеры, чем это плохо?
Я почему особо интересуюсь — в одном прошлом проекте я не смог найти способ избавиться от God Object в принципе — там в центре логики была сущность о ~200 методах и 5K строк. И как-то справлялись же...

есть множество вариантов декомпозиции объектов. Вы не смогли найти способ избавиться от God Object из-за его сложности, и это нормальная ситуация. Если бы изначально проектирование делалось из расчета SRP, God Objectа не возникло бы.

Если бы изначально проектирование делалось из расчета SRP, God Objectа не возникло бы.

Спасибо, фантастика типа «альтернативная история с попаданцами» меня не интересует.

А какого ответа вы, например, ожидали при таком смутном описании проблемы?

Во-первых, описания, в чём именно тут проблема, кроме чисто количественных характеристик, и я открытым текстом это спросил — чем плохо нарушение SRP, кроме чьей-то религии.
Во-вторых, если сильно повезёт, внятную инструкцию об общих подходах к нарезке God Object на части, коль он уже состоялся.
Вот это был бы конструктив. А «найди машину времени» — это просто шум.

ок, вынудили :)

Проблема в том, что при более чем одной ответсвенности в классе (взяв его за еденицу архитектуры, или модуль за еденицу — не важно), вам придется менять его, по двум различным причинам. Как известно, классы/модули взаимодействуют с другими классами/модулями, из чего следует, что в СВЯЗАННОМ классе вы КОСВЕННО имеете связь на более чем одну зависимость. Может так случится, что изменив часть функционала, вам придется менять код, который и близко не зависит от этого функционала, из-за вот таких связей. Это в свою очередь приводит к нелинейно возрастающей сложности системы: за счет сильной связанности (coupling), и слабой связности (cohesion). Надеюсь, понятно почему нарушение SRP увеличивает связанность и уменьшает связность. Отсюда можно уже шагать дальше по проблемам. И сложность добавляется не усложнением логики системы, а усложнением нефункциональной части системы — грубо говоря, механизмами взаимодействия ваших классов/модулей.

Как избавится — в первую очередь, инкапсулировать изменяющиеся части кода, изолировать их в классы/модули. т.е. если вам приходится менять 3 класса, при изменении одной какой-то функциональности, то их нужно поместить за фассадом или применить какой-то другой структурный паттерн, который позволить уменьшить связанность. Далее, по такому же принципу декомпозировать (инкапсулировать) ФУНКЦИОНАЛЬНОСТЬ внутри какждых таких модулей, увеличивая их связность.

Конечно, подходов к рефакторингу — масса.

OK. Теперь я сформулирую не вводную, а настоящую проблему. :) «Внимание, чёрный ящик» -
1. Действительно имеется God Object, примерно указанных размеров, или даже больше.
2. Формально, тем не менее, он отвечает за ровно одну сущность, просто эта сущность, мягко говоря, нетривиальна.
3. Всё, что мы смогли из него выделить отдельно, уже выделено (иначе он был бы ещё раза в 4 больше). Все попытки выделить что-то ещё без заведомо искусственных конструкций (которые усложняют, а не упрощают) — провалились.

И вот теперь вопрос: где и как искать общие принципы, которые, несмотря на всё сказанное, помогут сделать его меньше и понятнее? Как определить, если их нет, что их таки нет? Считать ли виноватыми исполнителей, или задача вообще не решаема? Я не хочу вываливать тонны специфики предметной области и реализации (часть из которых таки ДСП), но я хочу узнать, придумал ли кто-то, как решать хотя бы часть таких задач. В каких книгах это может быть отражено (стандартные отраслевые бестселлеры не упоминать, все прожёваны).

что же там за год обжект такой никак неделимый?

где и как искать общие принципы, которые, несмотря на всё сказанное, помогут сделать его меньше и понятнее?

А разве кто-то говорил, что должны быть безусловные и абсолютные принципы построения архитектуры?

А разве тот же SRP не выдвигается очень многими на эту роль?

Кем, например?

Да вот хотя бы коллегой Dmitry Shapar в этом топике.

разбить на уровни абстракции. Как: берёте ребёнка или человека, ни в зуб ногой не шарящего в вашей предметной области. И начинаете ему рассказывать, что там у вас происходит. Лучше ребёнка (лет не менее 10-и), а т.к он менее терпеливый, чем взрослый, надо будет рассказывать бегло и самую суть. Стараетесь рассказывать стройно, по порядку. В процессе попытки объяснить , что там у вас творится, вы будете выделять слои абстракции (как вы сами их видите). Таким естественным образом вы сможете разбить свой God Object на составляющие. Если, конечно, я угадала и это то, о чём вы спрашивали.

Переведи його із розряду God Object до розряду Structure. Та побудуй навколо неї або DSL (domain specific language), або систему трансформації/обробки даних, все це через API вистав назовні та радій. Якщо ООП не вирішує твою проблему, викидай його нафіг або мінімізуй його частку до мінімума.

Та побудуй навколо неї або DSL (domain specific language), або систему трансформації/обробки даних, все це через API вистав назовні та радій

Замедление раз в 10. Не могу.

Якщо ООП не вирішує твою проблему, викидай його нафіг

Вокруг его полно и не мешает. Но тут, благодаря комментариям, я таки убедился — весь ООП для банальностей, вроде SRP, надо таки игнорировать. Спасибо за подтверждение.

Ну, коли швидкість коду на першому місці, то тут один варіант — ігнорувати «канони» та «заповіти», та робити як треба, а не як «православніше» та «канонічніше».

я не пойму, вы серьзено хотите чтобы я тут накатал историю на овер9000 строк о том как оно бывает если не соблюдать SRP? )) пример же в двух словах не объяснишь, они очень разные

на овер9000 строк

Достаточно 5, я думаю. Но при этом оно должно быть не «не описали хитрые эффекты в документации» и не «тупой джун не дочитал документацию».

пример же в двух словах не объяснишь, они очень разные

Хм, автор топика хотя бы пыталась. Ну ладно, замнём.

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

автор топика имеет прокачанный левел SA

System Architect? Software Architect? System Analyst? System Administrator? Special Agent?
Я верю, что в вашей конторе есть какой-то консенсус про такие аббревиатуры, но Вы зря предполагаете, что все их понимают.

А вы хотите, чтобы вам «принципиальную схему серебрянной пули» в двух словах рассказали.

ДА! Пусть не в двух словах, а в 10000. Пусть не универсальную пулю на все случаи, но хотя бы для типичного случая земного воздуха. Почему банальщина типа всех этих LSP продумана и вообще считается обязательной к знанию, а как только поднимаемся на один уровень выше, сразу все подняли лапки и сдались?

только вот никто не берётся почему-то.

Никто не хочет прыгать выше головы, но все хотят сыр за (уже по 1000, с инфляцией)?

Почему банальщина типа всех этих LSP продумана и вообще считается обязательной к знанию, а как только поднимаемся на один уровень выше, сразу все подняли лапки и сдались
Потому что люди как раз зарплату за это получают, т.к. разбираются в таких вещах. А вы хотите бесплатно их знания и опыт. Тем более с вашим уровнем вежливости — good luck.
Потому что люди как раз зарплату за это получают, т.к. разбираются в таких вещах. А вы хотите бесплатно их знания и опыт.

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

Тем более с вашим уровнем вежливости — good luck.

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

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

Современный мир не сильно отличается от средневекового, все преследуют те же цели — личные. Только в средневековье было выгодно работать «на семью», а сейчас выгоднее работать «на себя и своё имя».

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

Только на критику :)

Только в средневековье было выгодно работать «на семью», а сейчас выгоднее работать «на себя и своё имя».

Ну вот, отличный кейс :)

Кстати, мы так и не заслушали начальника транспортного цеха — о том, что такое SA :)

Я думала, вы более догадливый (можно в линкедине посмотреть, в конце концов). Повышенным уровнем «объяснялочки» и «понималочки» по долгу службы из всех перечисленных вами персонажей обладает разве что System Analyst. Навыками аналитика также должен обладать System Architect — но это не главный его скилл.

Я думала, вы более догадливый

Не можете не ущипнуть :)

В linkedin я давно не хожу — бессмысленное место с безнадёжно кривым сайтом.

Повышенным уровнем «объяснялочки» и «понималочки» по долгу службы из всех перечисленных вами персонажей обладает разве что System Analyst. Навыками аналитика также должен обладать System Architect — но это не главный его скилл.

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

Странный человек. Сначала просит, чтобы рассказала, что это за две скромные буквы SA, а потом пишет, что «не пригодится». У вас удивительно вызывающий стиль общения, окружающим с вами наверняка нелегко.

Странный человек. Сначала просит, чтобы рассказала, что это за две скромные буквы SA, а потом пишет, что «не пригодится».

Я просил ответа про конкретное SA в вашем случае, а не рассказы про отличие System Architect от прочих в космических кораблях Большого театра или повышенные уровни «объяснялочки».

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

Тем, кто в своих фантазиях вместо реальности, и ещё и обижается, когда их приземляют — да, возможно. К счастью, таких немного.

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

На этом аргументе ad hominem, надеюсь, данная ветка закончится.

System Architect? Software Architect? System Analyst? System Administrator? Special Agent?
Архитектор. Как только человек заявляет, что у него прокаченный скилл архитектора, это значит что он только недавно выбился из джуниоров.
Почему банальщина типа всех этих LSP продумана и вообще считается обязательной к знанию, а как только поднимаемся на один уровень выше, сразу все подняли лапки и сдались?
Она не считается обязательной к знанию в приличном обществе. Это всё ясельные догмы, не суй пальцы в розетку, не лижи металлический столб на морозе, etc. В приличном обществе обязательным является наличие мозга и исключение догм и паттернов из речи и мышления.

The significant problems we face cannot be solved at the same level of thinking we were at when we created them ©

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

:) да, похоже :)

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

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

А BA в вашей трактовке — это однозначно Build Architect? По-другому быть не может? Шоры снимите.

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

Человек имел в виду, что маленькую область легче понять, чем большую. Это в значительной мере упрощает сопровождение, т.е. внесение будущих изменений и дополнений. Если полагаться на такие рассуждения — SRP и правда король (как и ISP, они очень похожи по своей природе).

Одна ответственность — одна причина для изменений. Поменял строку кода в GodObject и фиг поймёшь, на какие «части» системы это повлияло. А изменения в изолированных «одной ответственностью» областях приносят достаточно предсказуемые последствия.

Хотя на этот принцип тоже не следует полагаться всецело — излишний фанатизм до добра не доводит. Если слишком буквально применять SRP, система выйдет малопонятной и запутанной (из-за большого числа очень маленьких частей или из-за большого числа уровней абстракции). Как поступать — зависит от конкретной ситуации и здравого смысла проектировщика.

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

Вот и первый сексистский коммент подкатил

Это была подколка по поводу коммента: dou.ua/...itution-principle/#921774. Ко всем прочим комментариям при всём желании придраться по поводу было не возможно — а тут уже, если взять лупу, можно и рассмотреть чего-то такое.

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

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

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

ласкаем его по API
Это прекрасно )

Хм, я хоть тыщу лет уже не занимался программированием (с 2002 вроде бы), но пример с нагревалкой ...Создать класс с перекрываемыми пустыми методами инициализации , подогрева и выключения и переменными . На основе класса лепить что и как хочешь для А и В. Для С — создать наследника с методом проверки темп. воды.
А может сейчас подходы к ООП изменились ?

Пфф, ви миня таки забанили :-)

Где ? В ФБ ? 8-) было дело. 8-)
но в настоящий момент сам заблокирован толпой истиннорузкедидов , т.к. помел усомниться в великойпобедедидов и сделал это в очень нехорошей форме (для них)
Разбаню, страсти вроде утихли.

Плохой пример => плохие выводы и совершенно непонятное описание.

Во-первых, если setDesirableTemperature() для A и B ставит только локальное значение, то принципиальная разница начинается в реализации heat(), а именно она не показана. Для BrandA, BrandB heat() должна выглядеть примерно так:


for(;;) {
  $diff = getWaterTemperature() - this->desirableTemperature;
  if ($diff < 0) { setHeating(0); }
  else { setHeating(1); }
}

(про гистерезис промолчу, пока упростим без него)

а вариант для BrandC должен просто снимать температуру и сравнивать с желаемой. (А лучше — раз в минуту и ставить её заново, вдруг бойлер бутнулся.)

Далее, BrandC.getDesiredTemperature() не имеет права брать эти данные из устройства! BrandC.setDesiredTemperature() должен не только запоминать рекомендацию локально, а и выставлять в устройство.

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

Кстати, в статье на хабре всё совсем бредово, там они вообще не понимают, чего хотят (у них сначала BrandA и BrandB получают желаемую температуру в регистре, значит, они умеют её ставить, а потом появляется BrandC, который вдруг начал это уметь)... :( и в результате логика статьи такая же бредовая.

Сразу честно признаюсь, что ничего не понимаю в конструкции бойлеров, но ведь может, чисто теоретически, что бойлер устроен так, что если подать на него сигнал, он включается на коротких промежуток времени, и если в течение этого промежутка следующий сигнал не поступает, но выключается?
Возможно пример и не идеальный, но лично мне он показался достаточно понятным. Случай, с бойлером BRAND-C, и попыткой «в лоб» применить к нему устаревший дизайн — это вообще классика жанра. Мне кажется, я подобный код встречал бессчётное количество раз. Да и сам писал когда-то подобные «шыдэвры», чего уж греха таить.

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

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

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

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

создайте себе пустой интерфейс, а дальше уже можно реализовывать как хочешь
Такой подход тоже имеет право на существование. Проблемы, подобные описанной в примере чаще всего (ну, судя по моему опыту) возникают не на этапе проектирования, а когда приходится делать change requests, особенно если нужно править legacy code.
А на этом этапе перепроектировать всё заново уже слишком дорого. Нужно искать решение, которое позволит добиться цели самой малой кровью. И вот тут-то очень полезно держать в голове принцип Лисков. :)
А на этом этапе перепроектировать всё заново уже слишком дорого. Нужно искать решение, которое позволит добиться цели самой малой кровью. И вот тут-то очень полезно держать в голове принцип Лисков. :)

А чем он поможет такому рефакторингу?

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

Я бы на такое никогда не надеялся, потому что не знаешь, что и как потребуется расширять через год, когда 20-30% требований поменяются. Примерно столько же и придётся рефакторить, если это не самые основы предметной области.

И тут принцип Лисков ничем не поможет.

То что я описал в предыдущем комменте — только один из возможных случаев. В том то и дело, что при изначальном проектировании никогда нельзя угадать возможные последующие изменения (к примеру, какие девайсы изобретут через пару-восемь лет). Но вот когда ситуация, что называется дана в ощущениях, новый девайс появился и конкуренты его уже внедряют, тут может оказаться (а может конечно и не оказаться, всё зависит от того, как писался проект изначально), что самый дешёвый способ расшириться — не трогать старый код. А принцип Лисков в этом случае может помочь спроектировать новые классы так, чтобы они хорошо и естественно работали со старыми. Не уверен, что хорошо объяснил, но лучше не умею. Извините. :)

принцип Лисков в этом случае может помочь спроектировать новые классы так, чтобы они хорошо и естественно работали со старыми.

Принцип Лисков, если отбросить шелуху, говорит, что подклассы должны выполнять контракты, назначенные для базового класса. Это, по сути, всё. И если ваша новая функциональность укладывается в эти контракты — честь и хвала тому, кто угадал, но если она не может туда быть уложена — то сколько его ни пытайся выполнять, рефакторинг неизбежен.

И если ваша новая функциональность укладывается в эти контракты — честь и хвала тому, кто угадал
 Да, всё так, но есть один нюанс. Возможность уложить новую функциональность в старые схемы взаимодействия зависит не только от того, насколько она отличается от старой, но и от опыта, кративности и настойчивости проектировщика, которому приходится заниматься расширением. Я ничего не имею против рефакторинга. Но решение, что дешевле — рефакторить, или расширять, не трогая существующий код, — во многих случаях — задача очень творческая, со многими переменными. :) Кстати, принцип Лисков, на мой взгляд, полезно соблюдать и в том и в другом случае.
Да, всё так, но есть один нюанс. Возможность уложить новую функциональность в старые схемы взаимодействия зависит не только от того, насколько она отличается от старой, но и от опыта, кративности и настойчивости проектировщика, которому приходится заниматься расширением.

Вот именно, что «нюанс». Вы склоняете акцент в сторону возможности выкрутиться без расширения, и что она, вероятно, чаще, чем кажется — так? Я — в то, что это не абсолют. Тут общая позиция и есть, и нет: принципиального несогласия нет, но веса позиций сугубо субъективны, и по ним не договориться.

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

Угу.

По весАм договариваться действительно смысла нет. Я просто попытался привести несколько умозрительный пример, показывающий как хорошее понимание принципа Лисков может позволить сэкономить время и деньги. Я изначально не претендовал на то, что знаю что-нибудь о частоте применения того или иного подхода. Если честно, в практике я чаще всего встречал подход, описанный в примере с BRAND-C когда вместо вдумчивой оценки возможностей предпринимается попытка решить задачу «в лоб», в надежде на авось. Но это опять же ни о чём не говорит. ;)

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

С моей точки зрения, тут есть два пункта, на чём таки можно сэкономить:
1. Не нарушая его в субклассах — экономишь на днях и неделях отладки, когда что-то «вдруг пошло не так» из-за нарушения прописанного контракта.
2. (повторяюсь) когда это нельзя сделать, лучше отрефакторить, но не ломать этот принцип, а почему — см. пункт 1.

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

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

Дело вкуса конечно, но лично мне больше нравится когда материал подаётся небольшими порциями. «Жирные» сложные статьи прочесть и понять в один присест всё равно обычно не получается.

А жирность и не требовалась.

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

Вот так и получилось.

т.е. вы таки увидели ценность моей работы — это радует. А то, что по вашему мнению «осталось доработать совсем немного» — это ничего, в конце концов это лишь ваше персональное мнение. Хотя, конечно, ваша бурная ругливая манера выражать свои восторги — меня удивляет.

т.е. вы таки увидели ценность моей работы — это радует.

Можно и так сказать.

А то, что по вашему мнению «осталось доработать совсем немного» — это ничего, в конце концов это лишь ваше персональное мнение.

Вы себе ищете отмазку не дорабатывать?

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

Вы меня с кем-то попутали. Я максимально спокоен, и ещё и Вас успокаиваю, как могу :)

я не вижу, в чём моя «недоработка». Я считаю свою работу настолько прекрасной, насколько у меня получилось её создать. Вы думаете, если бы я могла лучше — я бы не сделала этого? Я публикуюсь на этом ресурсе совершенно бесплатно и по собственной инициативе, можете считать мой труд «волонтёрским проектом».

Вы думаете, если бы я могла лучше — я бы не сделала этого?

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

И вот это:

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

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

Я публикуюсь на этом ресурсе совершенно бесплатно и по собственной инициативе, можете считать мой труд «волонтёрским проектом».

Мы тут все в этом смысле волонтёры, а я и статьи для публичных ресурсов писал (не для DOU, но, может, и для него буду), но в любом случае это не основание считать само написание статьи каким-то альтруистическим деянием. Более того: единственно устойчивый источник это всегда самопиар, и стыдиться тут нечего — как и гордиться. А вот когда твой самопиар ещё и получает благодарности — это приятное дополнение.

сомнительные у вас какие-то благодарности.

Валентин, спасибо за поддержку, приятно, когда поддерживает гранд 8-)
По сути вопроса добавлю еще немного — принцип Лисков — базовый для ООП и был опубликован в нынешнем виде в 1994 году, но в абстрактном виде. оказывается, я его грыз еще в институтские годы (1990) по ее докладу — посмотрел в старый конспект. С чего его не знают нынешние разработчики — не пойму, база ведь . Впрочем — я слишком старый для «молодого,веселого коллектива со смузи в барбершопе и шариками в воркшопе с краудсорсингом»

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

Наследование вообще considered harmful. Данная статья с комментариями — тому подтверждение. Все вменяемые разработчики давно используют композицию.

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

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

Все вменяемые разработчики давно используют композицию.
Каким образом композиция заменяет наследование?
Композиция лишь один из видов наследования. Если конкретнее, наследование типа has-a. А старое доброе расширение класса (extend) — это is-a.
Это разные вещи.
Наследование вообще considered harmful.

Как и всё остальное, чего нет в Вашем любимом языке.

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

Ну, молодёжь приходит постоянно новая. И какие-то вещи они систематически пропускают, какие-то надо освежать в памяти. Кто-то какие-то вещи понимает интуитивно, но не может сформулировать (вообще, в программировании собственно сформулировать это уже часто больше половины решения задачи). В этом плане «старики» типа меня могут использовать все паттерны, например, просто из основ здравого смысла и каких-то самых базовых принципов вроде ТРИЗ, но при этом не помнить ни одного конкретного названия (так и было — я долго смотрел на тему паттернов как баран на новые ворота, но не из-за непонимания сути, а, наоборот, непонимания причин говорить об этом), а новички, наоборот, блукают в трёх соснах, спотыкаясь на каждом шагу, и им подобные описания вполне могут помогать.

Впрочем — я слишком старый для «молодого,веселого коллектива со смузи в барбершопе и шариками в воркшопе с краудсорсингом»

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

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

О ! Именно же.

$diff = getWaterTemperature() - this->desirableTemperature; if ($diff < 0) { setHeating(0); } else { setHeating(1); }

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

Я ж сказал — «примерно». Направления проверки выправились бы в первом же тесте :)

?

опровержение не может быть «примерным». Вы должны разнести меня в пух и прах. А заодно и принцип подстановки Барбары Лисков тоже.

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

Нет. Я показал, что пример неадекватен. Переделайте на адекватный пример, и тогда будет о чём говорить.

А заодно и принцип подстановки Барбары Лисков тоже.

А вот не надо мне приписывать свои домыслы.

я написала статью в соответствии со своими видениями. Если вам не нравится пример — напишите свой. Пока что я не увидела от вас ничего кроме «мне не нравится». Если вы знаете, как сделать лучше — делайте. Приведите нам такой пример, как надо.

Приведите нам такой пример, как надо.

Я его приведу в своей статье, коли она будет (если есть в этом смысл — уже достаточно обсуждений в Сети). Сейчас мы обсуждаем вашу.

я написала статью в соответствии со своими видениями.

Извините за придирку к словам, но «со своими видениями» тут, действительно, лучше всего сказано. Видения, да :)

«придирки к опечаткам и орфографии» — когда закончились все аргументы.

Статью жду.

«придирки к опечаткам и орфографии» — когда закончились все аргументы.

:)

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

Видение, кстати, ударение на первый слог.

Это другое слово, и оно обычно не используется во множественном числе, являясь типичным singularia tantum из-за своего смысла. А во множественном может использоваться то, которое «виде́ние».

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

Вы, разумеется, можете это слово писать хоть через все «и».

Могу. Но не хочу.

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

если вы не используете в своей речи каких-то слов — это не говорит, что все так делают.

Вы слово «обычно» не заметили? :)

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

Спасибо, до сих пор разбирался с этим, и не вижу, чем бы Вы тут помогли :)

я написала статью в соответствии со своими видениями
Коли вчиш чомусь то треба бути уважним щоб навчити правильним речам а не «віденію».
Приведите нам такой пример, как надо.
Java Collections: є інтерфейс Collection в якого є методи add(), contains(), clear() і так далі і є вагон реалізацій — ArrayList, LinkedList, HashSet, TreeSet, LinkedHashSet і так далі і всі вони працюють однаково в рамках контракта заданого в Collection. Напевне і в PHP шось подібне є. Якби ви дали такий приклад то уникнули би тони коментарів про дизайн ваших бойлерів.

это сухой и неинтересный пример.

Ліл. Зате він на 100% однозначний і там немає до чого придертися.

Якщо ви вже про машини згадали, то це був би кращий приклад, щось типу інтерфейсу Vehicle з методами accelerate(targetSpeed), turn(angle) в я кого є реалізації GroundVehicleOnWheels, GroundVehicleOnTracks — відповідно колісна та гусенична техніка яка містить різні реалізації прискорення та повороту, ну і типу контролер який рухає групою таких машинок.

Ваш бойлер бачите он скільки двозначностей містить?

Вы думаете, с машинами было бы по-другому? Тоже набежала бы куча «инакомыслящих» с претензиями, что там не такие классы. Я не зря написала PS — там же сказано, что примеры созданы исключительно для демонстрации LSP. Все, кому нужно, найдут для себя в этой статье что-то полезное. А критиканы — ну они всегда будут, как ни напиши.

Було бы по-другому якщо би ви ретельно свої приклади проаналізували на предмет можливих двозначностей :)

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

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

Взялся за гуж не говори что не дюж.

А тим більше в нашому суспільстві яке відразу налаштовано упереджено до матеріалу з сексистьских (дівчина!), технічних (похапе!), зверхніх (що ви тут понаписали, це діти в школі вчать) причин і готове закидати автора негативними коментарями.

в нашому суспільстві яке відразу налаштовано упереджено до матеріалу
- саме так. Але, на мій погляд, це скоріше недоліки суспільства. Доброзичливе суспільство має набагато більше шансів вчитися, переймати досвід, то ж воно більш дієздатне та конкурентноспроможне. Ми ж більше звикли займатися безглуздим критиканством. Замість обговорення теми посту ми обговорюємо тільки його недоліки. :)

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

Напевне, бо якби ви копнули глибше то хоча б дали нормальні назви брендам, там Valiant, Gorenje, Ariston і так далі замість “сухих и нетинтересных” BrandA, BrandB, BrandC =^_^=

А вот это уже может быть проблемой из-за реакции самих брэндов.

Лучше уж «Печки от Бабы-Яги», «Сковорода анлимитед» и «451 по Цельсию».

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

маркет-плейсмент
продакт-плейсмент
если вы знаете, как делать лучше — делайте
Уявіть що ви приходите в ресторан, вам подають блюдо яке зроблене об’єктивно неякісно, ви кличете керуючого чи кого там, пред’являєете претензії, а він вам каже «знаєете як приготувати краще — йдіть і готуйте самі». Не зовсім коректна аналогія у нашому випадку тому що я гроші не платив за перегляд статті, але думаю що суть ви вловите.

Именно. На данный момент вы пришли в бесплатный ресторан. И вас угощают. Если вам не нравится: “готуйте самі”. Это я вам и предлагаю своим “делайте”.

О ні, тут ресторанні критики лише рекомендують як приготувати страву наступного разу :) Доречним було би подякувати за конструктивні коментарі та пообіцяти що наступного разу все буде круто.

наступного разу не буде, у всякому разі не в найближчій час. В мене не вистачить нервів на всі ваші «конструктивні зауваження».

В данном случае «вы» — не персональное обращение, а скорее группа поддержки Конструктивные Задолбуши.

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

Уверенна, вы с лёгкостью найдёте новую жертву для реализации своих «благих намерений», не расстраивайтесь.

и даже более того — я предлагаю вам занять роль шеф-повара в этом «бесплатном ресторане» — угостите нас своим литературным шедевром, пожалуйста. Кто знает, может быть у вас выйдет лучше.

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

з методами accelerate(targetSpeed)

Интересно, а если машина не может достичь targetSpeed, тогда что?
А если предусмотрен такой случай, тогда у декоративной машины может быть speedLimit == 0 и LSP она в этом случае вполне удовлетворяет.

Интересно, а если машина не может достичь targetSpeed, тогда что?
Нічого, ви контракт для цього методу щойно самі собі придумали :)

Т.е. ваша машина умеет ускоряться до произвольного targetSpeed, я правильно понимаю? :)

Не совсем понятно, по какой причине должна появиться идея переопределить методы:

setDesirableTemperature
getDesirableTemperature
в классе
BrandCBoiler
? В описании функционирования этого бойлера об этом не сказано.
прежде чем писать пост, пожалуйста, задумайтесь, для какой цели были созданы эти примеры

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

вы можете придумать свой пример, если желаете.

Могу. Спасибо, кэп.

В описании функционирования этого бойлера об этом не сказано.

Там вообще дикая каша, но если распутать первоначальную задумку обоих авторов, то получится, что A и B не умеют сами поддерживать температуру (умеют только снимать), а C умеет поддерживать. Поэтому, после вызова setDesiredTemperature() установка желаемого должна вовремя (грубо говоря, за несколько секунд) попасть внутрь устройства типа C.

А дальше вопрос, как это делать. Можно в heat() её посылать каждый раз, но это криво. Можно посылать в переопределённом setDesiredTemperature(), это логически прямее всего, на на практике неустойчиво. Можно посылать по таймауту от предыдущей засылки (и это на практике, учитывая глюки, ребуты и т.п., самый надёжный метод), тогда в реализацию для C добавляется собственный жизненный цикл с коллбэками по таймауту; к этому методу можно добавить немедленную посылку из setDesiredTemperature(), а можно и не добавлять — ускорение на несколько секунд бойлеру непринципиально.

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

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

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

Т.е. хорошая иллюстрация с движками машин говорит о том, что или добавление нового движка может свестись к вариации на уровне данных (взяли старый класс движка, увеличили ему torque и hp и всё), или новый, весьма вероятно, продемонстрирует какой-то нестандартный нюанс управления, под что придётся переделать базовый интерфейс, для генерализации. Потому что «а мы тогда не могли предвидеть, что...» бойлер будет со встроенным термостатом / движок будет гибридным или электро / движков будет по одному на колесо — подставить нужное.

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

Грубые ошибки были допущены в самом начале.

1. Нарушение Single Responsibility Principle — класс Boiler используется и для хранения данных, фактически являясь DTO, и для взаимодействия с устрйоством
2. Что делать с heatWater? Вызывать его каждую миллисекунду или что? Вместо этого нужно добавить методы чтоб включить/отключить нагрев воды
3. Следуя SRP, нужно разделить интерфейсы считывания температуры и управления нагревательным элементом.

PS Минздрав предупреждает, PHP опасен для вашего здоровья.

2. Что делать с heatWater? Вызывать его каждую миллисекунду или что? Вместо этого нужно добавить методы чтоб включить/отключить нагрев воды
А может это блокирующий метод, который нагревает воду до определённой температуры?

Сложно представить хороший и полезный use case для такого метода.
Даже если он нужен, его можно реализовать на более высоком уровне.

если он нужен
хороше зауваження:) наприклад в нагрівачі С він пустий:)

Single Responsibility Principle гласит что в классе могут быть или только поля или только методы? Спасибо. Поржал. :)

Артём, а Вы точно со ссылочкой ничего не напутали? Если не напутали, то неплохо было бы как-то пояснить, что Вы имели ввиду. Там где-то написано, что в классе, в котором присутствуют поля не может быть методов? :)

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

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

Я вроде бы не утверждал, что в классе, в котором присутствуют поля, не может быть методов?
Вы фактически предложили вынести поле для хранения температуры в отдельный класс. В данном случае это — одно и то же. :) Поэтому мне стало интересно, во всех ли случаях классы, у которые есть методы для взаимодействия с другими классами, не могут содержать полей, или всё-таки есть случаи, когда это допустимо? Может быть я Вас не совсем правильно понял, но мне Ваша трактовка принципа единственной ответственности показалась слишком буквальной и несколько однобокой. Вот я и попросил Вас уточнить, как бы Вы действовали в подобном случае.
А за ссылки спасибо конечно, но мне интересно понять именно Вашу точку зрения. :)

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

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

Повторюсь:

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

Уже.

Минздрав предупреждает, PHP опасен для вашего здоровья.

Правильнее вот так:

Минздрав предупреждает, SOLID опасен для вашего здоровья.

Александр очень авторитетно заявляешь? на личном пример я так понимаю? не осилил js — опасность, ушел в Go, не осилил Unit тесты — опасность, перестал писать, не осилил collection chain methods — пишу обработку коллекций в циклах, не осилил высокоуровневого программирования — начал писать счетчики байтов пайпить их в сокеты — в принципе тут SOLID тебе не поможет, это да.

Зачем использовать сложные штуки, которые приносят больше вреда, чем пользы? Чтобы показать окружающим, насколько ты крут? Они это ниасилили, а ты асилил и спроектировал тонны бесполезных SOLID-говноабстракций, поднявших сложность hello world application до небес. Поздравляю! Ты действительно крут!

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

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

Дело в том, что SOLID и over-engineering — синонимы.

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

Все остальные принципы — либо банальщина, либо чушь и cargo cult, необходимый для повышения ЧСВ авторов этой чуши и их учеников.

SOLID, паттерны — это как раз штуки которые могут быть составной частью этой самой интуиции. Это интересно как раз с точки зрения структурирования опыта. Типа всегда делал так, или обходил острые углы, а тут это систематизировано. Конечно, если смотреть на это с точки зрения «заучил» и «пытаюсь формально впихнуть во все дыры», то получается фигня.

вы, ксати, так и не ответили, почему вы так не любите SOLID — из-за того, что вдоль и поперёк его изучили-поняли и считаете «примитивным наследием прошлых веков», или всё же потому, что вы обломали о него зубы, так и не поняв до конца «о чём это»? Ответьте, пожалуйста.

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

Подписаться на комментарии