Pull to refresh

Comments 17

Достаточно предоставить реализацию по умолчанию и ничего неожиданно падать не будет

Вот именно, Service Locator — это, конечно, достаточно неоднозначный паттерн проектирования, но называть его антипаттерном несправедливо. Пример с падениями больше про неудачное применение, потому что правильнее предоставить реализацию по умолчанию и возможность в одном месте (в точке инициализации приложения) переопределить отдельные аспекты поведения под себя. Основная проблема, про которую в статье упомянуто буквально вскользь, в том, что стабильность класса, который использует сервис локатор, становится неопределенной. Такой класс по сути не имеет нормального контракта (предусловий — требований к клиенту) и может пытаться использовать что угодно. В результате получается хрупкий дизайн, когда изменение любого поведения в проекте может затронуть произвольное количество классов и модулей.

Зато может неожиданно вызываться не то. Лучше уж пусть падает.

Поведение по умолчанию не может быть неожиданным - это часть контракта.

Я не понял, причем тут разработка игр: это достаточно общий шаблон, гляньте, например, на IServiceCollection в DependencyInjection.

Дальше: принципам SOLID что только не противоречит: прочекайте шаблоны на соответствие Open/Closed P, там от синглтонов/фабрик до фасадов и адаптеров одни несоответствия. Считать принципы ООП и SOLID гарантом качества как минимум наивно.

А если разбирать по-существу, то есть языки (например, C#), в которых очень хорошо сделана рефлексия, и было бы странно этим не пользоваться.

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

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

Кажется, научное сообщество сводится к тому что это очень близко к анти-паттерну, согласно книгам Р. Мартина и М. Симана. В первую очередь из-за ошибок времени вополнения. И конечно всё дерево связей создать один раз в compositionRoot не является возможным в более менее большом проекте, или DI будет заканчиваться именно там где начнутся new() по коду. Рано или поздно в любом проекте придется пересоздавать части графа и переиспользовать код. Не говоря уже о более мелких примитивах где напрашиваются абстрактные фабрики, знающие о скрытых зависимостях. Там где можно не писать тест на резолв, а увидеть сразу ошибку компиляции это всегда выигрыш. А если речь о большой распределённой команде, то отладка багов кривого графа связей это большая боль. Соответственно - если в смежной команде кто-то заюзал сервис локатор и выдает вам инструкции как зарегать его зависимости перед использованием кода, лучше сказать "не юзай антипаттерны" и отправить переделывать.

Самый чистый код - код который можно написать используя только иньекцию в конструктор и абстрактные фабрики. А потом уже для оптимизации написания их конструкторов можно заюзать DI контейнер.

Рефлексия - это набор еще более худших антипаттернов, так как нарушить SOLID принципы можно дуновением строки кода.

Ой, забавно, собственно оригинальную статью и написал Марк Симан.

Самый чистый код - код который можно написать используя только иньекцию в конструктор

А самая чистая комната - это комната на входе в которую лежат чьи-то трусы.

DI - это тонны копипасты ради эфемерной "явности", ломающей абстрагирование и усложняющей рефакторинг, и тормоза из-за инициализации всего мира ради одного метода.

Я в целом с вашим комментарием согласен, но хочется чуть уточнить:

>Считать принципы ООП и SOLID гарантом качества как минимум наивно.

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

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

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

А общий вывод ровно как у вас. Те кто считает это антипаттерном — догматики.

 Вот мне нужно поддерживать три версии фреймворка, у которых местами несовместимый API

Эх, по живому режете ведь :(

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

Тоже догма. Исходя из моей практики, в легаси хотябы 4-х летней давности (за исключением стандартных библиотек) я крайне редко переиспользовал написанные классы, а уж тем более расширял их (без полного переписывания, как нам O из SOLID гласит). Зато макросы 20-летней давности переиспользовал - только в путь.

Ну и раз все тут умные и блистают цитатами, сошлюсь ка я на Дядю Боба:

Customers are very good at "somehow knowing" what your design is. And they are choosing a new feature, that would completely fork your design. This is a fact of life...

Когда для ООП-шной архитектуры продумывают пути расширения, то обычно держат в голове что-то конкретное. Это конкретное в 95 процентах случаях радикально меняется уже через год, а половина оставшегося через два. И удобная для расширения штука становится препятствием вместо помощи. Сюда нам надо что-то протащить, вот тут что-то инкапсулированное поменять, а вот здесь сделать одного наследника, сам факт существования которога нарушит L из SOLID. И вот перед нами выбор, либо все наше "гибкое" переписывать к чертям, или вставлять "анитипаттерны" и ловить баги через один.

И это не потому, что мы дизайн плохой сделали, это потому, что мир меняется, рынок меняется, задачи меняются. И дизайн делался для одного продукта, а в процессе реализации продукт стал немного/совсем другим.

>Тоже догма.
Не, ну это как бы определение :) Эти методы предложили для достижения вот такой цели. Достигают ли они ее на самом деле — вопрос интересный, с вашими пояснениями я тоже скорее согласен, угадать что расширять, получается далеко не всегда.

Но все равно, требовать от SOLID достижения просто «качества вообще» (чего никто не обещал) — перебор.

Хм, классический ServiceLocator - это просто singleton, который реализует кучу методов типа getOrderValidator() - и в такой реализации большей части описанных проблемы не будет возникать. У такого решения, конечно, тоже дофига проблем (например то, что это синглтон), но в некоторых простых случаях это решение удобнее, чем какой-нибудь DI-контейнер.
Тут же рассматривается очень специфическая реализация, которая, действительно, не очень удачная. Впрочем, часть описанных проблем будут вообще у любых IoC решений - что не повод отказываться от IoC (да и замены особой не предложено).

Тут наверно нужно выяснить, а что такое антипаттерн?

Это нечто, что нежелательно использовать, дабы не поиметь сложностей.

А что такое паттерн?

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

Соответственно, в чем плюсы и минусы ServiceLocator?

Про минусы подробно рассказали выше. Плюс - простота связывания разных компонент между собой.

Когда плюсы проявляются сильнее минусов? Для относительно простых приложений. Когда полноценный DI избыточен, либо невозможен, а напрямую связывать несколько компонент между собой не хочется.

Когда плюсы проявляются сильнее минусов? Для относительно простых приложений.

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

А зачем дорабатывать имеющийся интерфейс, если можно добавить новый? Буква I в SOLID об этом говорит. Точно ли новый метод по бизнесу является чем-то относящимся к имеющемуся интерфейсу?

Автор либо не работал со сложными проектами, либо хочет хайпануть. Выглядит как подвид борьбы с MediatR, типа сложно найти бизнес-логику, не понятно, что аффектят изменения и т. д. Зато доступны пайплайны, декораторы и аспектное программирование в целом за счёт понятного контракта по точкам подключения. Могу посоветовать автору только не называть антипаттерном то, что он не использует. От себя могу сказать, что приложения с IOC и MediatR гораздо легче приспосабливаются к изменениям, типа замены интеграций, встраивания трассировки и метрик, публикации событий и т. д. За счёт возросшей сложности, да. Ошибки рантайма - дёшево ловятся на автотестах. Проблема из ничего.

Sign up to leave a comment.