Подтвердить что ты не робот

Инъекция зависимостей и шаблон Singleton Design

Как определить, когда использовать инъекцию зависимостей или одноэлементный шаблон. Я прочитал много сайтов, на которых они говорят: "Использовать зависимость для одноэлементного шаблона". Но я не уверен, согласен ли я с ними. Для моих небольших или средних проектов я определенно вижу, что использование одноэлементного шаблона прост.

Например, Logger. Я мог бы использовать Logger.GetInstance().Log(...)  Но вместо этого, зачем мне вводить каждый класс, который я создаю, с экземпляром журнала?

4b9b3361

Ответ 1

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

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

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

Имейте в виду, что объект может быть де-факто одинарным, но все равно быть получен через инъекцию зависимостей, а не через Singleton.getInstance().

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

Ответ 2

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

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

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

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

Ответ 3

Другие хорошо объяснили проблему с одиночными играми в целом. Я просто хотел бы добавить примечание о конкретном случае Logger. Я согласен с вами в том, что обычно не проблема в доступе к Logger (или корневому регистратору, если быть точным), как однотонный, с помощью статического метода getInstance() или getRootLogger(). (если только вы не хотите узнать, что регистрируется классом, который вы тестируете), но, по моему опыту, я едва ли могу вспомнить такие случаи, когда это было необходимо. И снова для других это может быть более насущной проблемой).

ИМО, как правило, singleton logger не вызывает беспокойства, поскольку он не содержит никакого состояния, относящегося к классу, который вы тестируете. То есть, состояние регистратора (и его возможные изменения) не влияет на состояние тестируемого класса. Таким образом, это не затрудняет тестирование вашего устройства.

Альтернативой было бы ввести регистратор через конструктор, чтобы (почти) каждый отдельный класс в вашем приложении. Для согласованности интерфейсов его следует вводить, даже если рассматриваемый класс не регистрирует ничего в настоящее время - альтернативой было бы то, что когда вы обнаружите в какой-то момент, что теперь вам нужно зарегистрировать что-то из этого класса, вам нужен логгер, таким образом вам нужно добавить параметр конструктора для DI, сломав весь клиентский код. Мне не нравятся оба этих варианта, и я чувствую, что использование DI для регистрации будет просто усложнять мою жизнь, чтобы соответствовать теоретическому правилу без какой-либо конкретной выгоды.

Итак, моя нижняя строка: класс, который используется (почти) универсально, но не содержит состояния, соответствующего вашему приложению, можно безопасно реализовать как Singleton.

Ответ 4

Это в основном, но не касается тестов. Singltons были популярны, потому что их было легко потреблять, но есть ряд недостатков в одиночных играх.

  • Трудно проверить. Смысл, как я могу убедиться, что журнал работает правильно.
  • Трудно тестировать. Если я тестирую код, использующий регистратор, но это не фокус моего теста, мне все равно нужно обеспечить, чтобы мой тест env поддерживал регистратор
  • Иногда вам не нужен синглтон, но более гибкий

DI дает вам легкое потребление ваших зависимых классов - просто поместите его в конструктор args, и система предоставит его вам, предоставляя вам гибкость тестирования и конструкции.

Ответ 5

О единственном времени, когда вы должны использовать Singleton вместо Injection Dependency, является то, что Singleton представляет неизменяемое значение, такое как List.Empty или тому подобное (при условии, что он является неизменным списком).

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

Ответ 6

Только что проверил статью Monostate - это отличная альтернатива синглтону, но она обладает некоторыми более простыми свойствами:

class Mono{
    public static $db;
    public function setDb($db){
       self::$db = $db;
    }

}

class Mapper extends Mono{
    //mapping procedure
    return $Entity;

    public function save($Entity);//requires database connection to be set
}

class Entity{
public function save(){
    $Mapper = new Mapper();
    $Mapper->save($this);//has same static reference to database class     
}

$Mapper = new Mapper();
$Mapper->setDb($db);

$User = $Mapper->find(1);
$User->save();

Разве это не страшно - потому что Mapper действительно зависит от соединения с базой данных для выполнения save() - но если ранее был создан другой картограф, он может пропустите этот шаг при приобретении его зависимостей. В то время как аккуратный, это также немного беспорядочно, не так ли?