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>();
}
Ответы
Ответ 1
Я только что разместил опровержение сообщения Джеффри Палермоса.
Короче говоря, мы не должны позволять конкретным деталям реализации влиять на наш дизайн. Это нарушит Принцип замещения Лискова в архитектурном масштабе.
Более элегантное решение позволяет нам сохранить дизайн, представив Lazy-load OrderShipper.
Ответ 2
Я опаздываю на встречу, но несколько быстрых очков...
Придерживаясь разветвления кода с использованием только одной зависимости, вы можете предложить две ветки:
- Применяя методы DDD, у вас не будет OrderProcessor с зависимостью от IOrderValidator. Вместо этого вы должны сделать объект Order() ответственным за собственную проверку. Или, придерживайтесь своего IOrderValidator, но он имеет свою зависимость в реализованном OrderShipper(), поскольку он вернет коды ошибок.
- Убедитесь, что вложенные зависимости построены с использованием подхода Singleton (и настроены как Singleton в используемом контейнере IoC). Это устраняет любые проблемы с памятью.
Как и кто-то другой, упомянутый здесь, необходимо разбить зависимость от конкретных классов и свободно связывать зависимости, используя Injection Dependency с использованием какого-либо используемого контейнера IoC.
Это делает будущий рефакторинг и замену устаревшего кода намного проще, ограничивая технический долг, возникший в будущем. Когда у вас есть проект около 500 000 строк кода, с 3000 модульными тестами, вы узнаете из первых рук, почему IoC так важен.
Ответ 3
Я думаю, что анти-шаблон здесь является чрезмерным использованием интерфейсов.