Как адаптировать шаблон спецификации для оценки комбинации объектов?
Я знаю, что шаблон спецификации описывает, как использовать иерархию классов, реализующих ISpecification<T>
оценить, соответствует ли объект-кандидат типа T определенной спецификации (= удовлетворяет бизнес-правилу).
Моя проблема: бизнес-правило, которое я хочу реализовать, должно оценивать несколько объектов (например, Заказчик и Контракт).
Мой двойной вопрос:
Существуют ли типичные адаптации шаблонов спецификации для достижения этой цели? Я могу думать только о снятии реализации
ISpecification<T>
моим классом спецификации, и принимая столько параметров, сколько я хочу вisSatisfiedBy()
метод. Но, делая это, я теряю способность комбинировать эту спецификацию с другими.Эта проблема показывает недостаток в моем дизайне? (то есть, что мне нужно оценить, используя Заказчика, а Контракт должен оцениваться по другому объекту, например, по Подписке, которая может содержать всю необходимую информацию)?
4 ответа
В этом случае (в зависимости от того, что конкретно должна делать спецификация, я бы использовал один из объектов в качестве субъекта спецификации, а другой (ие) в качестве параметра.
Пример:
public class ShouldCreateEmailAccountSpecification : ISpecification<Customer>
{
public ShouldCreateEmailAccountSpecification(Contract selectedContract)
{
SelectedContract = selectedContract;
}
public Contract SelectedContract { get; private set; }
public bool IsSatisfiedBy(Customer subject)
{
return false;
}
}
Решение Paco рассматривать один объект как субъект и один как параметр, используя инжекцию конструктора, может иногда работать, но если оба объекта создаются после объекта спецификации, это делает вещи довольно сложными.
Одним из решений этой проблемы является использование объекта параметра, как в этом предложении по рефакторингу: http://sourcemaking.com/refactoring/introduce-parameter-object.
Основная идея заключается в том, что если вы чувствуете, что как Заказчик, так и Контракт являются параметрами, представляющими связанную концепцию, тогда вы просто создаете другой объект параметров, который содержит их оба.
public class ParameterObject
{
public Customer Customer { get; set; }
public Contract Contract { get; set; }
}
Тогда ваша общая спецификация становится для этого типа:
public class SomeSpecification : ISpecification<ParameterObject>
{
public bool IsSatisfiedBy(ParameterObject candidate)
{
return false;
}
}
Ваша проблема заключается в том, что ваш интерфейс спецификации использует параметр общего типа, который не позволяет использовать его для объединения логики оценки между различными специализациями (Заказчик, Контракт), потому что ISpecification
ISpecification<Customer> cust_spec = /*...*/
ISpecification<Contract> contract_spec = /*... */
bool result = EvalWithAnd( () => cust_spec.IsSatisfiedBy(customer), () => contract_spec.IsSatisfiedBy( contract ) );
public void EvalWithAnd( params Func<bool>[] specs )
{
foreach( var spec in specs )
{
if ( !spec() )
return false; /* If any return false, we can short-circuit */
}
return true; /* all delegates returned true */
}
Я не знаю, понял ли я твой вопрос.
Если вы используете одну и ту же спецификацию как для Заказчика, так и для Контракта, это означает, что вы можете отправлять одинаковые сообщения им обоим. Эту проблему можно решить, заставив их оба реализовать интерфейс, и использовать этот интерфейс как тип T. Я не знаю, имеет ли это смысл в вашей области.
Извините, если это не ответ на ваш вопрос.