Я читал, что при использовании внедрения зависимостей следует избегать создания объектов с new
вне инжектора […].
Это верно лишь отчасти. Я покажу вам шаг за шагом, что new
имеет свое место, и что вполне можно использовать new
для реализации шаблона прототипа.
Начнем с очевидного: если нам нужен экземпляр типа B
, то он должен быть кем-то где-то создан. Допустим, у нас есть это:
class C
{
void Baz()
{
B b = new B(new A(…));
b.Bar();
}
}
Baz
требует B
для выполнения своей работы. Если мы хотим избежать new B(…)
, лучшее, что мы можем сделать, это удалить его из этого конкретного места в кодовой базе:
class C
{
C(Func<B> newB) // instead of Func<B>, we could also inject a B directly
{ // (the difference being that we would no longer control
this.newB = newB; // when the B gets created)
}
Func<B> newB;
void Baz()
{
var b = newB();
b.Bar();
}
}
Но B
, передаваемый конструктору C
, все равно должен быть где-то создан. Только теперь это где-то в другом месте.
Итак, что мы получили, избегая new
? C
больше не требуется иметь внутренние знания о том, как именно создать B
.
Но как Func<B> newB
(т.е. фабричный метод) сам создаст B
без использования new
? Кажется, мы не можем вечно уклоняться от new
.
Чтобы прояснить этот момент, давайте перейдем к другому, очень связанному примеру, который немного ближе к вашей проблеме (реализация шаблона прототипа в контексте DI): Абстрактные фабрики, еще один шаблон проектирования. Допустим, у нас есть BFactory
, единственной обязанностью которого является создание экземпляров типа B
:
interface BFactory
{
B CreateB();
}
Можем ли мы реализовать это без использования new
? Попробуем так же, как описано выше:
class RedundantBFactory : BFactory
{
RedundantBFactory(Func<B> newB)
{
this.newB = newB;
}
Func<B> newB;
public B CreateB()
{
return newB();
}
}
Это было бы абсолютно бессмысленно! Весь смысл существования фабрики заключается в том, что она инкапсулирует знания о том, как создавать экземпляры определенного типа. Только потому, что мы хотели избежать использования new
на нашей фабрике, мы внедрили именно это знание, сделав фабрику в целом полностью избыточной (потому что она просто перекладывает свою основную ответственность на другую сторону, которая должна выполнять аналогичную работу)!
Мы можем заключить, что разумно и уместно использовать new
внутри абстрактных фабрик и фабричных методов (например, в BFactory
или даже newB
выше), если мы не хотим, чтобы они были полностью избыточны:
class UsefulBFactory : BFactory
{
public UsefulAFactory(Func<A> newA)
{
this.newA = newA;
}
Func<A> newA;
public B CreateB()
{
return new B(newA());
}
}
Теперь к вашему шаблону прототипа: шаблон прототипа, по сути, касается клонирования объектов. То есть все типы, реализующие ваш интерфейс IAnalysis
, должны иметь возможность создавать клоны (копии) экземпляра. Как и в приведенном выше примере с абстрактной фабрикой, единственной целью вашего интерфейса является инкапсуляция некоторой формы создания объекта. В первую очередь это причина его существования, поэтому классы, реализующие этот интерфейс, не должны делегировать эту ответственность внешней стороне. Опять же, в этом случае вполне разумно использовать new
:
class W : IAnalysis
{
W(X x, Y y, …)
{
this.x = x;
this.y = y;
…
}
public IAnalysis CreateObject()
{
return new W(x, y, …);
}
}
И последнее замечание, просто чтобы подчеркнуть и завершить мое первоначальное заявление о том, что избегать new
не во всех случаях имеет смысл: обратите внимание, что в любом случае DI не следует использовать для всего.
Обычно вам нужно принять решение о том, какие типы должны обрабатываться контейнером внедрения зависимостей. Эти так называемые зависимости, или компоненты, или службы обычно абстрагируются как interface
или abstract class BaseClass
, так что вы, возможно, позже сможете заменить одну реализацию другой. Единственное место, где вы используете new Service(…)
, должно быть в корне композиции или (как показано выше) в абстрактных фабриках или фабричных методах (которые сами по себе являются зависимостями, которые будут внедряться туда, где вам нужно создавать объекты в выбранное вами время). Если бы вы щедро разбросали new Service(…)
по всему коду, было бы трудно заменить одну реализацию другой.
Но совершенно нормально использовать new
для создания примитивных значений и экземпляров типов значений (таких как string
, TimeSpan
и т. д.). Эти типы обычно не создаются контейнером внедрения зависимостей.
person
stakx - no longer contributing
schedule
08.10.2014
new
. Известно, что конструкторы очень полезны в DI (а именно для внедрения необходимых зависимостей). - person stakx - no longer contributing   schedule 08.10.2014new …
— это не одно и то же. (Последние создают новые объекты, используя первые; ноnew …
выражения не являются конструкторами.) Совет, который вы прочитали, скорее всего, касалсяnew
(потому что только тогда он имеет смысл ), но вы цитируете это так: я читал, что конструкторы должны…. - person stakx - no longer contributing   schedule 08.10.2014new
? Это означает, что следует избегать вызова конструкторов вне инжектора, если следует избегать создания объектов сnew
вне инжектора. Или есть возможность вызвать конструктор без нового? - person Paul Kertscher   schedule 08.10.2014new
, вы позволяете другой стороне создать объект для вас — вуаля, вы больше не используетеnew
самостоятельно! (Конечно, другая сторона может использоватьnew
, но вашему коду не нужно об этом знать и не заботиться; важно иметь доступную зависимость. То, что кто-то другой создал ее, приведет вас обратно к корень композиции.) Эта идея лежит в основе IoC-аспекта DI: вы неnew
устанавливаете зависимости, когда и где бы они вам ни понадобились — вместо этого вы их получаете/внедряете. - person stakx - no longer contributing   schedule 08.10.2014new
(не конструкторов!): вам не следует разбрасыватьnew SomeDependency(…)
по всей кодовой базе, а вместо этого концентрировать его в корне композиции (и, возможно, на фабриках, которые являются самими зависимостями, которые в идеале должны быть толькоnew
-ed в корне композиции). Также обратите внимание, что этот совет применим только к типамnew
, которые вы выбрали для внедрения (зависимости/компоненты/сервисы). Например, поскольку вы не хотите внедрять типы значений (такие какTimeSpan
),new
-введение этих допустимо. - person stakx - no longer contributing   schedule 08.10.2014new
создавать объекты, созданные фабрикой? - person Paul Kertscher   schedule 08.10.2014new
только для создания зависимостей. Если какой-то тип обладает внутренними знаниями о том, как построить объект, то использованиеnew
допустимо. Давайте посмотрим наFooFactory
. Весь смысл существования фабрики заключается в том, что она заключает в себе знания о том, как построить объект; он не должен делегировать эти знания внешней стороне. Если быFooFactory
не знал, как создаватьFoo
объекты, это было бы лишним. Так чтоnew Foo(…)
здесь в порядке. Но если ctorFoo
принимает дополнительные зависимости, ваша фабрика может также потребовать их, чтобы она могла их пересылать. - person stakx - no longer contributing   schedule 08.10.2014CreateObject
по существу предназначен для создания клона экземпляра. Поэтому какой бы тип ни реализовывал этот интерфейс, он должен иметь внутреннее знание того, как клонировать самого себя; это требование вытекает из намерения вашего интерфейса. Итак, опять же, использованиеnew TypeThatImplementsTheInterface(…)
здесь допустимо. - person stakx - no longer contributing   schedule 08.10.2014