Инверсия зависимостей. Создание объекта

Согласно принципам 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 слишком мало кода. Как вы думаете, что лучше?


person tov.Novoseltsev    schedule 26.08.2011    source источник
comment
Вариант 4 - правильный ответ: stackoverflow.com/questions/1943576/   -  person Mark Seemann    schedule 26.08.2011
comment
Однако почему вы хотите, чтобы Foo создавал IBar? Помните о дырявых абстракциях.   -  person Mark Seemann    schedule 26.08.2011


Ответы (5)


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

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

    private IBar bar;
}

interface IBar 
{
}
person Random Dev    schedule 26.08.2011

Для этого и создавались фабрики.

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

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

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

person James Gregory    schedule 26.08.2011

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

person J. Ed    schedule 26.08.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();
 //...
}
person George Polevoy    schedule 26.08.2011
comment
Это на самом деле не работает, не так ли? Вы не можете передать DefaultConstructorFactory<Bar> как IFactory<IBar> - person fearofawhackplanet; 09.12.2011

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

person Nilesh    schedule 26.08.2011