IoC и конструктор для разрешения анти-паттернов - PullRequest
10 голосов
/ 20 января 2010

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

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

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

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

В этом примере мы предполагаем, что _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
    }
}

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

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

Ответы [ 3 ]

22 голосов
/ 20 января 2010

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

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

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

1 голос
/ 20 января 2010

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

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

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

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

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

0 голосов
/ 20 января 2010

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...