Инверсия зависимостей.Создание объекта - PullRequest
8 голосов
/ 26 августа 2011

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

class Foo
{
    public Foo(IBar bar)
    {
        this.bar = bar;
    }

    private IBar bar;
}

interface IBar 
{
}

class Bar: IBar 
{
}

Но что, если я хочу, чтобы мой класс Foo мог создавать классы Bar, не зная точной реализации IBar? Я могу придумать 4 решения, но все они имеют недостатки:

  1. внедрение типа объекта и использование отражения
  2. с использованием Generics
  3. с использованием "Service Locator" и вызовом метода Resolve ().
  4. создание отдельного класса фабрики и внедрение его в Foo:

class Foo
{
    public void DoSmth(IBarCreator barCreator) 
    {
        var newBar = barCreator.CreateBar();
    }
}

interface IBarCreator 
{
    IBar CreateBar();
}

class BarCreator : IBarCreator
{
    public IBar CreateBar()
    {
        return new Bar();
    }
}

Последний случай кажется естественным, но класс BarCreator имеет слишком маленький код. Итак, как вы думаете, какой из них лучше?

Ответы [ 5 ]

3 голосов
/ 26 августа 2011

Это то, для чего были созданы фабрики.

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

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

Для удобства некоторые контейнеры позволяют указать фабрику, которая будет использоваться контейнером при создании экземпляра компонента. В этом случае ваш класс может напрямую зависеть от IBar, но ваш контейнер будет вызывать IBarCreator, когда ему потребуется новый экземпляр. Замок Виндзор имеет методы UseFactory и UseFactoryMethod в своих API, например.

3 голосов
/ 26 августа 2011

Мне нравится "вводить" Func<IBar> в этом случае. Вот так:

class Foo
{
    public Foo(Func<IBar> barCreator)
    {
        this.bar = barCreator();
    }

    private IBar bar;
}

interface IBar 
{
}
2 голосов
/ 26 августа 2011

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

Сделайте его многоразовым с помощью дженериков:

interface IFactory<T>
{
 T Create();
}

class DefaultConstructorFactory<T> : IFactory<T>, where T: new()
{
 public T Create() { return new T();}
}

Или использовать анонимную функцию как фабрику:

public void DoSomething(Func<IBar> barCreator)
{
 var newBar = barCreator();
 //...
}
2 голосов
/ 26 августа 2011

все зависит от вашего точного сценария и ваших потребностей.
Я думаю, что наиболее используемый подход, как вы упомянули, фабрика .
Если вы используете инфраструктуру IoC (например, Ninject или Sprint.Net, Castle Windsor и т. Д., См. здесь ), также может быть приемлемым поиск службы.

0 голосов
/ 26 августа 2011

Я чувствую, когда вы говорите «Я хочу, чтобы мой класс Foo мог создавать Бар», в игру вступает еще одно правило - «Разделение проблем».Поэтому вам следует делегировать задачу создания класса чему-то другому, и Foo не следует беспокоиться об этой задаче.

...