Значения по умолчанию для аргументов конструктора в проекте библиотеки - PullRequest
6 голосов
/ 29 марта 2012

Я пишу библиотеку, которая будет предоставлять коллекцию открытых типов для своих потребителей.

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

В примере (C #):

public class Samurai {

    private readonly IWeapon _weapon;

    // consumers will use this constructor most of the time
    public Samurai() {
        _weapon = ??? // get an instance of the default weapon somehow
    }

    // consumers will use this constructor if they want to explicitly
    //   configure dependencies for this instance
    public Samurai(IWeapon weapon) {
        _weapon = weapon;
    }
}

MyПервое решение было бы использовать шаблон локатора службы.

Код будет выглядеть так:

...
public Samurai() {
    _weapon = ServiceLocator.Instance.Get<IWeapon>();
}
...

У меня есть проблема с этим, хотя.Сервисный локатор был помечен как анти-шаблон ( ссылка ), и я полностью согласен с этими аргументами.С другой стороны, Мартин Фаулер выступает за использование шаблона поиска служб именно в этой ситуации (библиотечные проекты) ( ссылка ).Я хочу быть осторожным и исключить возможную необходимость переписать библиотеку после того, как обнаружится, что локатор службы действительно был плохой идеей.

Итак, в заключение - считаете ли вы, что локатор службы в этом сценарии подходит?Должен ли я решить мою проблему совершенно по-другому?Любая мысль приветствуется ...

Ответы [ 3 ]

4 голосов
/ 29 марта 2012

Если вы хотите облегчить жизнь пользователям, не использующим DI-контейнер, вы можете предоставить экземпляры по умолчанию через выделенный класс Defaults, который имеет такие методы:

public virtual Samurai CreateDefaultSamurai()
{
   return new Samurai(CreateDefaultWeapon());
}

public virtual IWeapon CreateDefaultWeapon()
{
   return new Shuriken();
}

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

1 голос
/ 12 июня 2014

Я использовал следующий подход в моем проекте на C #. Цель состояла в том, чтобы добиться внедрения зависимостей (для модульного / фиктивного тестирования), не нанося вред реализации кода для «нормального варианта использования» (то есть имея большое количество новых (), которые каскадно проходят через поток выполнения).

public sealed class QueueProcessor : IQueueProcessor
{
    private IVbfInventory vbfInventory;
    private IVbfRetryList vbfRetryList;

    public QueueProcessor(IVbfInventory vbfInventory = null, IVbfRetryList vbfRetryList = null)
    {
        this.vbfInventory = vbfInventory ?? new VbfInventory();
        this.vbfRetryList = vbfRetryList ?? new VbfRetryList();
    }
}

Это позволяет использовать DI, но также означает, что любому потребителю не нужно беспокоиться о том, каким должен быть «поток экземпляров по умолчанию».

1 голос
/ 29 марта 2012

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

public interface IWeaponProvider
{
    IWeapon GetWeapon();
}

public class Samurai
{
    private readonly IWeapon _weapon;

    public Samurai(IWeaponProvider provider)
    {
        _weapon = provider.GetWeapon();
    }
}

Теперь вы можете предоставитьЛокальный поставщик по умолчанию для оружия:

public class DefaultWeaponProvider : IWeaponProvider
{
    public IWeapon GetWeapon()
    {
        return new Sword();
    }
}

И так как это локальное значение по умолчанию (в отличие от одного из другой сборки, так что это не «инъекция ублюдка»), вы можете использовать его как частьваш класс самураев, а также:

public class Samurai
{
    private readonly IWeapon _weapon;

    public Samurai() : this(new DefaultWeaponProvider())
    {
    }

    public Samurai(IWeaponProvider provider)
    {
        _weapon = provider.GetWeapon();
    }
}
...