Какой шаблон лучше использовать для внедрения зависимостей, когда вам также нужна конкретная зависимость по умолчанию? - PullRequest
0 голосов
/ 09 ноября 2018

Я использовал оба приведенных ниже паттерна для тестируемости, и мне интересно, какой из них лучше ООП / ТВЕРДЫЙ.

Шаблон 1: укажите два конструктора, один из которых создает конкретную зависимость

public MyClass
{
    private IDependency dependency;

    public MyClass() : this(new ConcreteDependency()) { }

    public MyClass(IDependency dependency)
    {
       this.dependency = dependency;
    }
}

Шаблон 2. Базовый класс имеет инициализатор зависимостей, а производный класс вызывает его с конкретной зависимостью

public abstract MyClassBase
{
    private IDependency dependency;

    protected void Initialize(IDependency dependency)
    {
        this.dependency = dependency;
    }
}


public MyClass : MyClassBase
{
    public MyClass()
    {
       Initialize(new ConcreteDependency());
    }
}

Ответы [ 2 ]

0 голосов
/ 09 ноября 2018

В обоих приведенных примерах используется анти-паттерн Control Freak , как описано в Внедрение зависимостей, Принципы, практика, паттерны второго издания .

Происходит анти-паттерн Control Freak :

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

В первом примере, хотя MyClass использует абстракцию IDependency, она перетаскивает ссылку на конкретный компонент ConcreteDependency, в результате чего два класса тесно связаны .

Такая же тесная связь происходит во втором примере, и это фактически тот же самый анти-паттерн. Кроме того, во втором примере даже используется метод Initialize для применения зависимости к уже созданному экземпляру MyClassBase. Это вызывает Temporal Coupling , который сам по себе пахнет кодом.

Правильное решение предотвращает сильную связь и временную связь, что означает, что вы используете Конструктор Injection и определяете только один конструктор :

public MyClass
{
    private IDependency dependency;

    public MyClass(IDependency dependency)
    {
       if (dependency == null) throw new ArgumentNullException("dependency");
       this.dependency = dependency;
    }
}
0 голосов
/ 09 ноября 2018

Недостатком ваших двух шаблонов является то, что они могут нарушить Принцип обращения зависимостей : MyClass - более абстрактная вещь, но она знает о ConcreteDependency, что более конкретно. Действительно:

  1. Где бы вы ни использовали класс MyClass, класс ConcreteDependency должен быть доступен.
  2. Более того, если вам нужно изменить ConcreteDependency на ConcreteDependency2, то ваш класс MyClass должен быть изменен, что не очень хорошо в некоторых ситуациях, например: есть клиенты MyClass, которые никогда не используют это конструктор по умолчанию.

Не говоря уже о том, что шаблон # 2 может привести ваши объекты в неожиданное состояние, потому что производные классы могут забыть вызвать метод Initialize, если ваша документация не очень хорошая.

Я предлагаю использовать Factory Pattern:

class Factory {
    public MyClass createDefault() {
        return new MyClass(new ConcreteDependency());
    }
}
...