Внедрение зависимости и Шаблон разработки Singleton

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

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

75
задан Bozho 18 April 2010 в 16:01
поделиться

7 ответов

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

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

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

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

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

56
ответ дан 24 November 2019 в 11:34
поделиться

Примерно единственный раз, когда вы должны когда-либо использовать синглтон вместо внедрения зависимостей, - это если синглтон представляет неизменяемое значение, такое как List.Empty или подобное ( предполагая неизменяемые списки).

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

2
ответ дан 24 November 2019 в 11:34
поделиться

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

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

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

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

17
ответ дан 24 November 2019 в 11:34
поделиться

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

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

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

9
ответ дан 24 November 2019 в 11:34
поделиться

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

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

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

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

84
ответ дан 24 November 2019 в 11:34
поделиться
0
ответ дан 24 November 2019 в 11:34
поделиться

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

1
ответ дан 24 November 2019 в 11:34
поделиться