Насколько я понимаю, YAGNI рекомендует нам не выделять абстракцию без необходимости. То есть, если нам не нужен полиморфизм в данный конкретный момент, то нам не следует выделять абстракцию, ибо зачем тогда? Однако и OCP, и DIP призывает нас выделить абстракцию здесь же. OCP это советует сделать для того, чтобы если вдруг нам понадобится изменить поведение класса, мы это могли сделать не изменяя тип, просто передав новую реализацию абстракции в тип. DIP же прямым текстом сообщает, что детали должны зависеть от абстракций и не наоборот.
Также выделять абстракцию может заставить необходимость тестирования пользователей типа. Но в рамках java, как я понимаю, такой необходимости нет.
Так вот, нужно ли выделять абстракцию сразу же? Нужно ли следовать OCP и DIP?
Ответ
Разные принципы проектирования направлены на решение определенной задачи проектирования, и в некоторых случаях они могут противоречить друг другу.
Можно сказать, что разные принципы «тянут» дизайн в разные стороны и нужно найти правильный вектор, наиболее полезный в данном конкретном случае: SRP – говорит о простоте решения, OCP – об изоляции компонентов модулей, DIP – о «правильности» отношений между классами, а LSP – о «правильном» полиморфизме.
Следование одному принципу может привести к нарушению другого. Так, например, любое наследование можно рассматривать как нарушение SPR, поскольку теперь за одну ответственность (рисование фигур) отвечает целая группа классов. Следование DIP и OCP могут привести к появлению дополнительных «швов», т.е. интерфейсов/базовых классов в системе, что, опять-таки, приведет к нарушению SRP и/или ISP.
Но такое отношение между принципами не является фиксированным. Для простого случая выделение иерархии фигур для рисования является нарушением SRP, поскольку «рисование» в первой итерации может заключаться в выводе текста на консоль и размазывание этой информации по нескольким классам будет избыточным. Но по мере усложнения решения, появление иерархии наследования будет оправданной с точки зрения SRP, поскольку сложность отображения каждой отдельной фигуры будет столь высокой, что понятие «ответственности» тоже поменяется. Если вначале «единой ответственностью» было отображение всех фигур, то теперь одна ответственность будет разбита на множество: «отображение круга», «отображение квадрата» и т.п.
Принцип YAGNI (You Aren’t Gonna Need It) – это более фундаментальный принцип («принцип высшего порядка» или «метапринцип»), который поможет понять, когда следовать принципам/паттернам/правилам, а когда нет.
В основе принципа YAGNI лежит несколько наблюдений:
Программисты, как и люди в целом, плохо предсказывают будущее.
Ни одно гибкое решение не будет достаточно гибким.
Эти наблюдения приводят к следующим выводам: попытка создать гибкое решение на ранних стадиях разработки обречено на создание переусложненного решения. Связано это с тем, что на ранних этапах еще не известно, какие именно изменения в системе потребуются, и просто не понятно, где «подстилать солому» для будущих изменений.
Поскольку на ранних этапах мы не знаем, какая именно гибкость нужна, мы заложим гибкость не там, где нужно: мы предусмотрим замену слоя доступа к данным, но из-за «дырявых абстракций», мы все равно залочим решение на определенной базе данных, или же такая гибкость просто никогда не понадобиться. Мы создадим «фреймворк» парсинга аргументов командной строки, который будет использоваться в одном приложении, а стоимость прикручивания его в другое приложение будет таким большим, что никто этим заниматься не будет.
Хороший дизайн заключается в простоте решения, когда изменения требований ведет к линейным трудозатратам.
Проще всего добиться этого путем эволюционного дизайна: мы начинаем с разбиения системы на крупные компоненты, но не занимаемся выделением лишнего. Не нужны базовые классы, если сейчас нет хотя бы 2-х-3-х наследников. И даже если такие наследники «могут появиться в будущем», то выделить иерархию типов нужно именно тогда, когда это самое будущее настанет.
Принцип YAGNI можно выразить следующим образом: выделение лишних абстракций (и любое другое усложнение) оправдано лишь в том случае, если стоимость их выделения в будущем будет существенно дороже, чем сейчас.
Инвестиции в продуманность интерфейса программирования библиотеки (API) – будут оправданны, поскольку стоимость внесения изменений очень высока. Стоимость же выделения интерфейса/базового класса приложении является практически одинаковой сегодня или через год.
Решение проблемы по мере поступления позволяет сосредоточиться на задачах, актуальных сегодня и позволяет избежать работы, которая может и не понадобиться совсем.
P.S. Ну и мне кажется, что у вас не совсем правильное понимание принципов OCP и DIP, которые совсем не сводятся к необходимости применения наследования.
Вот несколько статей по теме:
Шпаргалка по SOLID принципам
Open-Closed Principle
Dependency Inversion Principle
Критический взгляд на DIP
И отдельно, в статье "О принципах проектирования" я рассматриваю примерно то же самое, что и в этом ответе: что слепое следование принципам приведет к переусложненному и тяжелому в сопровождении решению.
Комментариев нет:
Отправить комментарий