Если Синглтоны плохие, то почему Service Container хорош?

Мы все знаем, как bad Singletons связаны с тем, что они скрывают зависимости и другие причины.

Но в рамках может быть много объектов, которые должны быть созданы только один раз и называются от везде (logger, db и т.д.).

Чтобы решить эту проблему, мне сказали использовать так называемый "Диспетчер объектов" (или Service Container, например symfony), который внутренне хранит все ссылка на Службы (регистратор и т.д.).

Но почему провайдер услуг не так плох, как чистый Синглтон?

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

PS. Я знаю, что чтобы не скрывать зависимости, я должен использовать DI (как указано Misko)

Добавить

Я бы добавил: в наши дни синглтоны не так злы, создатель PHPUnit объяснил это здесь:

DI + Singleton решает проблему:

<?php
class Client {

    public function doSomething(Singleton $singleton = NULL){

        if ($singleton === NULL) {
            $singleton = Singleton::getInstance();
        }

        // ...
    }
}
?>

который довольно умный, даже если это не решает никаких проблем.

За исключением DI и Service Container есть ли приемлемое решение для доступа к этим вспомогательным объектам?

Ответ 1

Локатор сервисов - это всего лишь два из двух зол, чтобы сказать. "Меньше" кипит до этих четырех различий (по крайней мере, я не могу сейчас думать о других):

Принцип единой ответственности

Сервисный контейнер не нарушает принцип единой ответственности, как Синглтон. Singletons смешивает создание объектов и бизнес-логику, в то время как Service Container несет строгую ответственность за управление жизненными циклами объекта вашего приложения. В этом отношении Service Container лучше.

Сцепление

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

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

Скрытые зависимости

Тем не менее проблема скрытия зависимостей существует. Когда вы просто вводите локатор в свои классы потребления, вы не будете знать никаких зависимостей. Но в отличие от Singleton, SL обычно создает все зависимости, необходимые за кулисами. Поэтому, когда вы пользуетесь Сервисом, вы не оказываетесь в Misko Hevery в примере CreditCard. вам не нужно создавать экземпляры зависимостей вручную.

Извлечение зависимостей внутри экземпляра также нарушает Закон Деметры, в котором говорится, что вы не должны копаться в коллаборационистах. Экземпляр должен говорить только с его непосредственными сотрудниками. Это проблема как с Singleton, так и с ServiceLocator.

Глобальное состояние

Проблема глобального состояния также несколько смягчается, потому что когда вы создаете новый локатор сервисов между тестами, все ранее созданные экземпляры также удаляются (если только вы не допустили ошибку и не сохранили их в статических атрибутах в SL). Конечно, это не относится к любому глобальному состоянию в классах, управляемых SL, конечно.

Также смотрите Fowler на "Локатор сервисов" и "Инъекция зависимостей" для более углубленного обсуждения.


Заметка о вашем обновлении и связанная статья Себастьян Бергманн по тестированию кода, который использует Singletons: Себастьян, никоим образом, не предлагает что предлагаемое обходное решение делает использование синонов менее проблемой. Это всего лишь один из способов сделать код, который в противном случае был бы невозможным проверить более подверженную тестированию. Но это все еще проблематичный код. Фактически, он прямо отмечает: "Просто потому, что можешь, а не значит, что должен".

Ответ 2

Шаблон локатора службы - это анти-шаблон. Это не решает проблему разоблачения зависимостей (вы не можете сказать, глядя на определение класса, каковы его зависимости, потому что они не вводятся, вместо этого они вытаскиваются из локатора службы).

Итак, ваш вопрос: зачем нужны локаторы? Мой ответ: они не являются.

Избегайте, избегайте, избегайте.

Ответ 3

Сервисный контейнер скрывает зависимости как шаблон Singleton. Возможно, вы захотите предложить использовать контейнеры для инъекций зависимостей, поскольку у него есть все преимущества контейнерного обслуживания, но нет (насколько мне известно) недостатков, которые имеет контейнер обслуживания.

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

Это хороший вопрос в SO, объясняющий разницу обоих: В чем разница между шаблонами зависимостей зависимостей и шаблонов Locator?

Ответ 4

Поскольку вы можете легко заменить объекты в Service Container, 1) наследование (класс Object Manager может быть унаследован, а методы могут быть переопределены)
2) изменение конфигурации (в случае с Symfony)

И, синглтоны плохи не только из-за высокой связи, но и потому, что они _ Single _tons. Это неправильная архитектура для почти всех видов объектов.

С "чистым" DI (в конструкторах) вы заплатите очень большую цену - все объекты должны быть созданы перед передачей в конструкторе. Это будет означать более используемую память и меньшую производительность. Кроме того, не всегда объект может быть просто создан и передан в конструкторе - может быть создана цепочка зависимостей... Мой английский недостаточно хорош, чтобы полностью обсудить это, прочитайте об этом в документации Symfony.

Ответ 5

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

Например, у меня есть front-end и admin. Внутри администратора я хочу, чтобы они могли войти в систему как пользователь. Рассмотрим код внутри администратора.

$frontend = new Frontend();
$frontend->auth->login($_GET['user']);
$frontend->redirect('/');

Это может установить новое соединение с базой данных, новый журнал и т.д. для инициализации интерфейса и проверить, действительно ли пользователь существует, действителен и т.д. Он также будет использовать надлежащие отдельные службы cookie и определения местоположения.

Мое понятие о синглетоне - вы не можете добавить один и тот же объект внутри родителя дважды. Например,

$logger1=$api->add('Logger');
$logger2=$api->add('Logger');

оставит вас с одним экземпляром и обе переменные, указывающие на него.

Наконец, если вы хотите использовать объектно-ориентированную разработку, то работайте с объектами, а не с классами.