Комментарии 26
А мне, как начинающему тестировщику, такой формат понятнее, чем куски кода. И я очень благодарна авторам, которые берутся вот так "на пальцах" и на картинках объяснять те вещи, которые вам - более опытным ребятам, могут казаться очевидными.
Open-Close не правильно изображен. Вместо процесса переделки должен создаваться второй робот, с обеими функциями, при этом первый оствется не тронутым и не останавливает свою работу.
И скорее даже первый робот торчит из второго вместо режущей руки.
Не обязательно.
SOLID — это не про ООП в условной Java — это шире.
Например есть консольная утилита sort
— сортирует строки по не убыванию, и много утилит и людей использует её такой какая она есть.
Но тут становится понятно что иногда мы получаем не тот результат что нам нужен:
$ sort << EOF
> 1 one
> 2 two
> 10 ten
> 20 twenty
> EOF
10 ten
1 one
20 twenty
2 two
Нам хочется что бы числа интерпретировались именно как числа при сортировке (а не набор символов) что бы получать "математически правильную" сортировку.
У нас есть 2 пути — нарушить SOLID и изменить поведение утилиты — но тогда десятки/сотни уже написанных утилит (использующих sort
) перестанут корректно работать.
Или не нарушать SOLID и расширить функциональность (добавив в данном случае опцию -g
):
$ sort -g << EOF
1 one
2 two
10 ten
20 twenty
EOF
1 one
2 two
10 ten
20 twenty
И даже если мы говорим про классы — принцип Open-Close говорит нам, что если у нас есть условный класс Foo в версии 1.0, то в версии 1.1 у него могут появиться новые методы (или опциональные параметры к старым), но старые методы должны работать так как и раньше. И не обязательно для этого создавать новый класс потомок.
По большому счету Open-Close — это про обратную совместимость.
Разве для того, чтобы добавить новую сортировку разработчикам не понадобилось модифицировать sort? В пределе, это приводит к созданию комбайнов (что нарушает SRP), появлению мертвого кода (когда в программе требуется только сортировка по числам, то не зачем тащить балластом сортировку по строкам) и т.п.$ sort -g << EOF
По большому счету Open-Close — это про обратную совместимость.
SOLID это набор принципов декомпозиции и он не сбалансирован. Декомпозиция расширяет возможности для повторного использования кода. Но с другой стороны, она приводит к увеличению количества деталей, что затрудняет обучение и нередко усложняет синтез решения.
`sort -q` это пример разумного отступления от принципов SOLID в пользу упрощения.
Хорошим примером OCP является `git [command]`, где каждая команда реализуется отдельным файлом с именем `git-*`. Чтобы добавить новую команду не нужно менять сам `git` и ждать нового релиза. Достаточно создать новый файл с командой и поместить в PATH. Сам же `git` это просто единая точка входа для запуска таких команд и вывода подсказок.
Для начинающих стоит отметить, что солид — не панацея и не отменяет того, что писать надо прежде всего для людей, а не для машины. А то часто залезаешь в проект — формально всё по солиду, а фактически хочется запихать его авторам туда, откуда они его достали.
По большому счету Open-Close — это про обратную совместимость
Все же, нет. Данный принцип не зря сформулирован именно так, как сформулирован.
В нем не говориться «Классы следует расширять», но
«Классы должны быть открыты для расширения, но закрыты для модификации.»
То есть, код, который этому принципу следует, попросту не позволит взять и переписать поведение. Но позволит расширить функциональность через наследование или агрегацию.
На обратную совместимость это только на первый взгляд похоже.
Это не значит, что если код ещё никто не видел, то класс нужно модифицировать, а не расширять. И это не значит, что если мы класс пишем с нуля, то можем в него напихать 50 методов, а вот если он уже написан — то методы добавлять нельзя.
И конечно, глупость считать, что ОпенКлоуз — про обратную совместимость. Она нужна далеко не всегда, а тут внезапно влезла в базовый принцип проектирования (которое часто вообще происходит до того, как у нас появляется хоть какое-то Legacy).
Более того, отсутствие обратной совместимости никак не противоречит принципам Solid.
Пример: у меня есть контракт, который следует принципам SOLID, в нём метод GetItem.
interface IContract {
Item GetItem();
}
Теперь я понимаю, что есть более подходящее название для этого метода и произвожу рефакторинг:
interface IContract {
Item GetOrCreateItem();
}
Код всё так же следует принципам SOLID (что OCP, что LSP), а обратная совместимость — сломана.
Вы указали:
Каждый класс должен отвечать только за одну операцию.
В оригинале не совсем так. Такой принцип есть, но как утверждает сам автор «Uncle Bob», это не SRP.
Под SRP понимается следующее:
Модуль должен отвечать за одного и только за одного пользователя или заинтересованное лицо
Окончательная версия выглядит так:
Модуль должен отвечать за одного и только одного актора
В книге есть пример про клаcc Employee и 2 метода reportHours() и calculatePay(). Вроде все нормально, это все касается работника. Но на деле это может нарушать SRP по той причине, что разные отделы отвечают за эти методы.
reportHours() — Бухгалтерия
calculatePay() — Отдел по работе с персоналом.
И если делать изменения в соответствие с запросом одних, могут пострадать другие, если возьмем например тот факт, что оплата считается по часам.
В данном контексте "актор" — это источник изменений, т.е. тот человек/отдел/роль от которого поступают требования на изменение данного класса. И идея данного принципа в том, чтобы избежать конфликта в требованиях — вероятность которого сильно увеличивается если требования поступают от разных источников, у которых отличаются намерения и приоритеты.
Разве человек, который не способен понять SOLID из словесного описания и нескольких примеров сможет в дальнейшем манипулировать в своей голове объектами и понятиями достаточно хорошо, чтоб писать приличный код?
- Судя по тому, что SOLID регулярно постоянно обсуждают — всё это не так просто и однозначно, полученную информацию можно по разному понять, интепретировать и использовать. Причём обсуждают и примерами кода, и словесными описаниями, а тема никак не утихает.
- На эту того, что такое "приличный код" в холиварах тоже сломано немало копий.
- Навыки "понять из словесного описания" и "хорошо манипулировать в своей голове объектами и понятиями" (а это два отдельных, но связанных скилла) являются именно навыками — т.е. чем-то, что можно развить. Кому-то и такой формат в картинках может пойти на пользу, чтобы развить соотвествующий навык для дальнейшего использования.
Так что, имхо — нет, не движение на дно, а с точностью до наоборот.
:-(
тот момент когда в картинках понял, а в коде не знаешь как реализовать. )))
Принцип единственной ответственности это когда класс имеет единственное назначение(а не операции). Операция,- это по своей сути действия обьекта. Например экскаваторщик может иметь два операционных набора, водить экскаватор и копать экскаватором.
Принцип открытости и закрытости означает что класс открыт для расширения, но закрыт для изменения. Дополнять класс методами само по себе не запрещено, но если расширять класс так как вы показали , вы автоматически нарушаете и принцип единственной ответственности и принцип разделения интерфейсов. Да и есть спорный момент- , при внесении в класс дополнительные публичные методы , о классе приходится знать больше чем оно необходимо. Например прогер знал о 2 методах в классе, то при расширении класса, прогеру придется обновлять эти знания. По своей сути класс должен единственный раз написан для продакшена, и в последующем закрыт для изменений и прямого расширения(по крайней мере его публичные части). Единственные причины для изменение класса -⅞ это работа над ошибками, рефакторинг и поддержка совместимости, не ломая API взаимодействия.
поэтому для расширения класса в ооп доступна способность наследования, а лучше применение наследования через паттерн композиции.
Принцип постановки Лисков
Это правило нынче уже более современно звучит и с новыми терминами.
По сути правило постановки лисков объединили с паттерном Контракты.
Да и если эту концепцию поддерживает язык ООП , то о нем сильно не стоит заморачиваться.
Помимо вами сказанного, реализуются дополнительные правила при переопределении класса дочерним классом:
Контравариантность типов параметров метода в подтипе. Или предусловия не могут быть усилены в подтипе. Т.е. типы параметров метода дочернего класса могут заменяться родительскими типами параметров метода основного класса.
Ковариация типов возвращаемых значений метода в подтипе. Или постусловия не могут быть ослаблены в подклассе. Т.е. тип результата метода дочернего класса может заменяться дочерним типом результата метода основного класса.
Инвариант не может быть ослаблен в подтипе. Т.е. тип свойства дочернего класса может иметь дочерний тип свойства осноного класса.
Новые исключения не могут быть созданы методами дочернего класса, за исключением случаев, когда они являются подтипами исключений, созданных методами основного класса.
(исторические ограничения)неизменяемые данные базового класса не должны быть изменяемыми в подклассе. Если данные изменяемы, изменение таких данных правильней производить через методы основного класса.
-(конец перечислений. Далее редактор с параграфами хрень исполняет.)
По сути правило постановки лисков объединили с паттерном Контракты.
Принципы SOLID в картинках