Подтвердить что ты не робот

IoC и конструктор с чередованием

Этот вопрос является результатом сообщения Джеффри Палермо о том, как обойти разветвленный код и инъекцию зависимости http://jeffreypalermo.com/blog/constructor-over-injection-anti-pattern/

В своем посте Jeffery имеет класс (public class OrderProcessor : IOrderProcessor), который имеет два интерфейса в конструкторе. Один из них - это валидатор IOrderValidator и IOrderShipper. Его ветки кода метода только после использования методов на интерфейсе IOrderValidator и никогда ничего не используют в интерфейсе IOrderShipper.

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

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

В этом примере мы предполагаем, что _validator.Validate(order) всегда возвращает false, а метод IOrderShipper.Ship() никогда не вызывается.

Оригинальный код:

public class OrderProcessor : IOrderProcessor
{
    private readonly IOrderValidator _validator;
    private readonly IOrderShipper _shipper;

    public OrderProcessor(IOrderValidator validator, IOrderShipper shipper)
    {
      _validator = validator;
      _shipper = shipper;
    }

    public SuccessResult Process(Order order)
    {
      bool isValid = _validator.Validate(order);
      if (isValid)
      {
          _shipper.Ship(order);
      }
      return CreateStatus(isValid);
    }

    private SuccessResult CreateStatus(bool isValid)
    {
        return isValid ? SuccessResult.Success : SuccessResult.Failed;
    }
}

public class OrderShipper : IOrderShipper
{
  public OrderShipper()
  {
      Thread.Sleep(TimeSpan.FromMilliseconds(777));
  }

  public void Ship(Order order)
  {
      //ship the order
  }
}

Реализованный код

public class OrderProcessor : IOrderProcessor
{
    private readonly IOrderValidator _validator;

    public OrderProcessor(IOrderValidator validator)
    {
      _validator = validator;
    }

    public SuccessResult Process(Order order)
    {
      bool isValid = _validator.Validate(order);
      if (isValid)
      {
          IOrderShipper shipper = new OrderShipperFactory().GetDefault();
          shipper.Ship(order);
      }
      return CreateStatus(isValid);
    }

    private SuccessResult CreateStatus(bool isValid)
    {
        return isValid ? SuccessResult.Success : SuccessResult.Failed;
    }
}   

public class OrderShipperFactory
{
    public static Func<IOrderShipper> CreationClosure;
    public IOrderShipper GetDefault()
    {
        return CreationClosure(); //executes closure
    }
}

И вот метод, который настраивает этот factory во время запуска (global.asax для ASP.NET):

private static void ConfigureFactories()
{
    OrderShipperFactory.CreationClosure =
        () => ObjectFactory.GetInstance<IOrderShipper>();
}
4b9b3361

Ответ 1

Я только что разместил опровержение сообщения Джеффри Палермоса.

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

Более элегантное решение позволяет нам сохранить дизайн, представив Lazy-load OrderShipper.

Ответ 2

Я опаздываю на встречу, но несколько быстрых очков...

Придерживаясь разветвления кода с использованием только одной зависимости, вы можете предложить две ветки:

  • Применяя методы DDD, у вас не будет OrderProcessor с зависимостью от IOrderValidator. Вместо этого вы должны сделать объект Order() ответственным за собственную проверку. Или, придерживайтесь своего IOrderValidator, но он имеет свою зависимость в реализованном OrderShipper(), поскольку он вернет коды ошибок.
  • Убедитесь, что вложенные зависимости построены с использованием подхода Singleton (и настроены как Singleton в используемом контейнере IoC). Это устраняет любые проблемы с памятью.

Как и кто-то другой, упомянутый здесь, необходимо разбить зависимость от конкретных классов и свободно связывать зависимости, используя Injection Dependency с использованием какого-либо используемого контейнера IoC.

Это делает будущий рефакторинг и замену устаревшего кода намного проще, ограничивая технический долг, возникший в будущем. Когда у вас есть проект около 500 000 строк кода, с 3000 модульными тестами, вы узнаете из первых рук, почему IoC так важен.

Ответ 3

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