Комментарии 28
Особенно радует что автор, который наверняка посвятил много лет изучению темы, явно дает понять вначале, что для небольших и средних проектов практики могут и не подойти, что нужно тщательно взвесить зачем и как это поможет — вот об этом как раз обычно все и забывают.
Я же мучаюсь с DI на проекте, разбитом на кучу маленьких микросервисов. Толку от него в таком масштабе нет вовсе, зато найти нужный класс и метод целая проблема, потому что все зависит от абстракций и прыгнуть не выйдет.
object ModuleNameServices : Services{
val serviceX by services{ServiceX()} //cached
val serviceY by services("key"){ServiceY()} //scoped
val serviceZ get() = ServiceZ() //newInstance
val serviceU = AnotherModuleService //ref
}
Сам же класс Services — под капотом хэшмеп для хранения скоуп и хэшированых сервисо + функции возвращающие делегаты (services), ну и для тестирования — ф-ция для моков (положить в хэш сервис) — обертки и тд.
А использую через конструктор
class SomeClass(services: Services = ModuleServices){
val sX get()= services.serviceX // Вот тут важно get()= - защити от рантайм модификации и статических ссылок
}
Плюс такого подхода — прямая связност кода (ктрл+клик и найти вожно всё), легко тестить, нереально запутаться, нету рантайм ошибок, нету кодгенерации.
Минус — не по клинархитектуре… нету интерфейсов и тд… хотя при желании можно и доработать
Я же мучаюсь с DI на проекте, разбитом на кучу маленьких микросервисов. Толку от него в таком масштабе нет вовсе, зато найти нужный класс и метод целая проблема, потому что все зависит от абстракций и прыгнуть не выйдет.
А что мешает использовать тот же DI, но отказаться от абстракций, а все бины делать и инжектить сразу как классы? Ведь в случае если у абстракции одна реализация, зачем делать преждевременную и излишнюю абстракцию, это типа известного принципа Хоара/Кнута о том, что «преждевременная оптимизация — корень всех зол», но для абстракций?
прыгнуть не выйдет
В Идее Ctrl+Alt+B — сразу переход к реализации.
А что мешает использовать тот же DI, но отказаться от абстракций,
А модульные тесты (unit tests) как под это писать? «Как по книжке» — реализовать интерфейсы имитаторами (mock) и заглушками (stub) — уже не получится.
Нет, способы тестирования и при таком подходе, конечно есть, потому как были времена когда интерфейсов (тех, которые ключевое слово interface) не было, но тестировать все равно надо было: типовой прием — подмена исходного файла с реализацией. Но все ли разработчики такие приемы (без которых нынче легко прожить) знают? И все ли языки и исполняющие системы это дозволяют? И как использовать фреймворки для тестирования под написание таких тестов, буде такое желание возникнет: они ведь «как по книжке» умеют работать, вроде как, все, а вот как по-другому — возможны варианты, неприятные.
Короче, как и при любом отклонении от общепринятой колеи — вопросы, вопросы…
А что мешает использовать тот же DI, но отказаться от абстракций
Проект на ноде, главный разработчик пришел из c#. Для программистов c# и java зависеть от абстракций это нечто неоспоримо правильное, ведь гибкость, модульность, и не важно что ни один класс не будет переиспользоваться с какими-то другими зависимостями никогда, потому что в теории по книжкам это единственно верный путь. А для меня вот это вот все чуждо, я хочу обратно свой KISS, но времена такие, cишарперы массово мигрируют на ноду, это не шуточки, серьезно, очень много людей приходит из c# и насаждают свои паттерны.
зато найти нужный класс и метод целая проблема, потому что все зависит от абстракций и прыгнуть не выйдетВот эта проблема убивает одну из важных фич современных IDE, созданных вообще-то для удобства написания кода. :-)
А ещё чтобы понять откуда идут вызовы, приходится пробираться через десяток классов, пробрасывающих одни и те же данные в разном виде (часто встречаю маппинг сущностей с одинаковыми полями из одного слоя в другой).
Не стоит бояться длинных имен, ...
Постойте-ка, а как же правило «не раскрывать деталь реализации»? Я всегда старался давать длинные имена переменным и функциям, а потом на меня обратили внимание более опытные и начали «ругать»; говорили, что я не умею выделять абстракции.
Взять, например, то же самое имя метода из этой же статьи: transformDateToString. А важно ли к имени метода добавлять деталь реализации (toString), корректно ли это и не нарушает ли это те самые правила, о которых все твердят? Разве не правильным будет именовать метод проще: transformDate?
Буду очень рад прочитать другое мнение на этот счёт.
«не раскрывать деталь реализации»
transformDateToString — не раскрывает РЕАЛИЗАЦИЮ
transformDateToStringUsingSimpleFormatter — а вот такой — уже лишнее
transformDate
само по себе не содержит контекста действия, трансформ как? зачем? во что? Такое имя было бы уместно только внутри класса ToStringUtils/ ToStringTransformer — так как тут уже класс является контекстом.
Опять же — имя должно быть лаконичным, но с учетом контекста. При нейминге я беру(стараюсь) одно из 2 правил:
1) это общепринятые и известные имена (i j для циклов и тд)
2) имя отражает логику / цель использования (с учетом контекста использования)
‘trasformDate()’ прям напрашивается входящий параметр «а во что». Мне кажется, более логично метод экземпляра как раз назвать ‘toString()’, так как мы уже знаем, что мы преобразуем. И ещё добавить в вызов параметры при необходимости (формат, локаль и т.д.) с дефолтной реализацией.
В данном случае toString — это суть, а не деталь реализации.
Нет не является, т.к. и Date и String вообще жёстко прибиты сигнатурой метода ;)
Действительно дурацкое имя.
Дату к строке приводят не потому что захотелось, а с какой-то целью: вывести в UI (и тогда это что-то типа .toHuman), залогировать (но логгер сам должен уметь это делать в конкретном формате), отправить через API (тогда нужен или timestamp, или isostring, т.е. то, что описано в контракте).
Получается так. Но если совсем избегать наследования то не получится делать полиморфизм. Впрочем нет, получится при желании, но будет выглядеть как «без сахарочка». Плюс при композиции придется деинкапсулировать все protected в public, то есть открыть больше чем при наследовании. То есть страдают все киты ООП. Может тогда не ООП вам нужно?
OCP не только про наследование. Он про расширяемость в целом. Вот неплохой пример https://towardsdatascience.com/5-principles-to-write-solid-code-examples-in-python-9062272e6bdc
И у Мартина похожий пример есть, хотя он его больше к SRP относит. Думаю, это потому что принципы связаны.
Вы не задумывались о монетизации этого опыта — что-то типа курса переподготовки jun -> middle с практическими задачами на каждый паттерн? Сам бы с удовольствием прошёл :)
0x7E5 Рассуждения о главном