Внедрение зависимостей и шаблон Singleton Design - PullRequest
72 голосов
/ 18 апреля 2010

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

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

Ответы [ 7 ]

83 голосов
/ 18 апреля 2010

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

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

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

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

53 голосов
/ 18 апреля 2010

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

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

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

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

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

17 голосов
/ 19 апреля 2010

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

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

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

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

9 голосов
/ 18 апреля 2010

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

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

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

2 голосов
/ 19 апреля 2010

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

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

1 голос
/ 24 апреля 2010

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

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 (), но если ранее был создан другой маппер, он может пропустите этот шаг в получении его зависимостей. В то время как опрятный, это также немного грязно, не так ли?

0 голосов
/ 21 апреля 2010

Существуют и другие альтернативы синглтону: шаблоны Proxy и MonoState.

http://www.objectmentor.com/resources/articles/SingletonAndMonostate.pdf

Как использовать шаблон прокси для замены одиночного?

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