Комментарии 37
Принцип подстановки Барбары Лисков можно сформулировать так:
Для создания взаимозаменяемых частей эти части должны соответствовать контракту, который позволяет заменять эти части друг другом.
Вот интересно, гдеж вы взяли такую формулировку этого принципа? В вики есть формулировки от Барбары, и от Роберта, и обе они, что характерно, используют термин «подтип» (subtype). Причем как в русской редакции, так и в английской. А вы это слово куда дели?
Можно изучать что угодно хоть по клочку туалетной бумаги — лишь бы источник был указан достоверный
Во-первых, там слово "подтип" есть, как раз на этой самой 77й странице.
Во-вторых, взятый вами фрагмент относился к назначению принципа, а не к его формулировке (формулировки этого принципа на 77й странице нет).
Не реже, а чаще, особенно для небольших и средних проектов, где накопипастить код в контроллер намного проще и понятнее чем выдумывать что-то хитрое.
небольших и средних проектов
Да, но только, если проект «выстрелит» потом убить кучу времени разбор такого кода, чтобы запилить очередную фичу, которая нужна «еще вчера».
Пожалуйста, объясните, как принцип единственной ответственности применяется на верхних уровнях иерархии системы?
Ну, функция main() в С/С++ программе, которая делает много всего. Типа Adobe Photoshop.
Компонент Main — это конечная деталь, политика самого низкого уровня.
Он является точкой входа в систему. От него ничего не зависит, кроме работоспособности системы. Его задача — создать все Фабрики, Стратегии
и другие глобальные средства и затем передать управление высокоуровневым абстракциям в системе.
Роберт Мартин, «Чистый код».
Я думал, Вы про микросервисы, к примеру.
А можно простейший пример? Пусть есть файл(ы) в каждом из которых первая строка некий идентификатор (наименование параметра), потом числа в столбик (для простоты любой конкретное число, например 3). Программа должна просто посчитать среднее по числам из файла и вывести результат на консоль с указанием названия параметра.
А теперь предположим, что после написания этой программы, в файлах добавилась строчка с единицами измерений (штуки, килограммы...) и нам их тоже нужно напечатать…
Если я правильно понял автора (и S в SOLID), то именно поэтому это не принцип "единственной ответственности", а принцип единственной зависимости от требований. То есть верхние уровни иерархии системы ответственны за одно требование, обычно какое-нибудь общее и не должны меняться от изменения компонентов/требований ниже.
Разбираясь с SRP, я обратил внимание на следующее: в оригинале (Robert Martin — Clean Architecture), финальное определение принципа звучит так:
A module should be responsible to one, and only one, actor.
Обратите внимание на фразу "responsible to", которую на русский язык все переводят как "ответственный за". Я не эксперт в английском, но, если не ошибаюсь, "ответственный за" переводится на английский как "responsible for". На сколько я понимаю, предлог "to" в оригинале говорит не об ответственности "за что-то", а об ответственности "перед кем-то/чем-то". Тогда, на мой взгляд, этот принцип на русском языке становиться намного понятней:
Модуль должен быть ответственным перед единственным актором (потребителем).
Т.е. другими словами, модуль нужно проектировать так, чтобы он не обслуживал сразу нескольких потребителей, а только одного.
То есть "одна функция должна быть вызвана только в одно месте".
А если этот модуль используется несколькими другими модулями?
Ох уж этот SOLID. В нем только LSP четко описан, но не Мартином, а самой Лисков, рекомендую читать оригинал.
Модуль должен иметь только одну причину для изменения.
Ню ню, смешно, что это подаётся как некая «мудрость» опытными разработчиками, да ещё и в форме догмы. Я понимаю когда джуны читают и радуются, им может казаться, что вот он, ответ на вопрос «как проектировать системы». Ну давайте посмотрим на SRP внимательнее.
«Причиной для изменений» может явиться любое событие в будущем. “Событие/причина» это множество «элементарных событий/причин» (вспоминаем тер. вер.) Очевидно, что если нам нужно иметь строго одну причину для изменений, то этой причиной может быть только «элементарное событие». Разложить всю реальность на элементарные события мы не умеем. Приехали. Можно выкручиваться, но тогда нужно реальность заменить моделью, в которой каких-то событий не будет. Например там может не быть переезда с Linux на Windows или задачи типа «нужно все ускорить в 1000 раз на том же железе». Но в реальности то все это и многое другое есть. В итоге SRP тем проще при унять, чем меньше об окружающей действительности знает применяющий, идеальное оружие воинствующей невежественности.
Мне больше нравится «принцип понятной цели» или ППЦ! Он гласит — «после прочтения текста модуля достаточно подготовленный читатель должен понять, что этот модуль делает и (важно) как эта деятельность соответствует деятельности других модулей»
Могу поспорить, что мой один ППЦ помощнее всего SOLID будет.
достаточно подготовленный читатель
Какими критериями разработчик должен обладать, чтобы стать ДПЧ?
Когда ДПЧ завалит задачу можно будет решить, либо он не ДПЧ, либо модуль не ППЦ и скорректировать ситуацию :)
А у бухгалтерии ничего не поменялось, они считают зарплаты по тем же формулам, вот только в этой формуле теперь будут другие цифры, потому что calculateSalary() ходит в calculateOvertimeHours(), а там теперь по просьбе отдела кадров сверхурочные не 2, а 2,5. Упс…
То есть проблема в том, что код не покрыт тестами. При чём тут SRP?
Но в чистой архитектуре такой подход недопустим, поскольку нарушается Принцип открытости-закрытости — старый код не должен меняться.
То есть OCP создаёт проблему на ровном месте, ок.
Как решить проблему и сделать так, чтобы при добавлении новой реализации не трогать старый код? Конечно же, через интерфейсы.
И далее вы рассказываете как решить эти проблемы, применяя инверсию контроля.
Для создания взаимозаменяемых частей эти части должны соответствовать контракту, который позволяет заменять эти части друг другом.
Какое-то масло маслянное. Барбара точно это говорила?
В этом и есть смысл Принципа подстановки Барбары Лисков. Любой компонент системы должен работать с компонентами, от которых он зависит, через неизменяемый контракт.
Неизменяемый контракт? Мы точно про LSP говорим, а не про OCP?
Теперь сущность Person нельзя удалить! PersonService реализует интерфейс, в котором не объявлено ничего лишнего.
Замечательно, потом у нас появляются сущности, которые нельзя обновлять, потом которые нельзя читать, потом которые нельзя создавать. В результате мы приходим к тому, что все интерфейсы должны состоять из ровно одного метода. При этом пользователь всё равно может послать DELETE запрос к сущности, которая вроде как никакого удаления не поддерживает. А чтобы узнать поддерживает ли конкретный сервис удаление или нет, придётся использовать рефлексию. Только для того, чтобы кинуть исключение не в методе сервиса, а где-то в недрах веб-фреймворка.
Стройте зависимости от абстракций. Не стройте их от реализаций.
А где тут инверсия? (закрываю глаза ладонями) А нету инверсии!
Советую почитать ещё тут: http://sergeyteplyakov.blogspot.com/2014/08/single-responsibility-principle.html
Понятнее о S.O.L.I.D