Спецификация (шаблон проектирования)
«Спецификация» в программировании — это шаблон проектирования, посредством которого представление правил бизнес логики может быть преобразовано в виде цепочки объектов, связанных операциями булевой логики.
Этот шаблон выделяет такие спецификации (правила) в бизнес логике, которые подходят для «сцепления» с другими. Объект бизнес логики наследует свою функциональность от абстрактного аггрегирующего класса CompositeSpecification, который содержит всего один метод IsSatisfiedBy, возвращающий булево значение. После инстанцирования, объект объединяется в цепочку с другими объектами. В результате, не теряя гибкости в настройке бизнес логики, мы можем с лёгкостью добавлять новые правила.
Примеры кода
C#
public interface ISpecification
{
bool IsSatisfiedBy(object candidate);
ISpecification And(ISpecification other);
ISpecification Or(ISpecification other);
ISpecification Not();
}
public abstract class CompositeSpecification : ISpecification
{
public abstract bool IsSatisfiedBy(object candidate);
public ISpecification And(ISpecification other)
{
return new AndSpecification(this, other);
}
public ISpecification Or(ISpecification other)
{
return new OrSpecification(this, other);
}
public ISpecification Not()
{
return new NotSpecification(this);
}
}
public class AndSpecification : CompositeSpecification
{
private ISpecification One;
private ISpecification Other;
public AndSpecification(ISpecification x, ISpecification y)
{
One = x;
Other = y;
}
public override bool IsSatisfiedBy(object candidate)
{
return One.IsSatisfiedBy(candidate) && Other.IsSatisfiedBy(candidate);
}
}
public class OrSpecification : CompositeSpecification
{
private ISpecification One;
private ISpecification Other;
public OrSpecification(ISpecification x, ISpecification y)
{
One = x;
Other = y;
}
public override bool IsSatisfiedBy(object candidate)
{
return One.IsSatisfiedBy(candidate) || Other.IsSatisfiedBy(candidate);
}
}
public class NotSpecification : CompositeSpecification
{
private ISpecification Wrapped;
public NotSpecification(ISpecification x)
{
Wrapped = x;
}
public override bool IsSatisfiedBy(object candidate)
{
return !Wrapped.IsSatisfiedBy(candidate);
}
}
C# 3.0, упрощённый через шаблоны и методы расширения
public interface ISpecification<TEntity>
{
bool IsSatisfiedBy(TEntity entity);
}
internal class AndSpecification<TEntity> : ISpecification<TEntity>
{
private readonly ISpecification<TEntity> _spec1;
private readonly ISpecification<TEntity> _spec2;
protected ISpecification<TEntity> Spec1
{
get
{
return _spec1;
}
}
protected ISpecification<TEntity> Spec2
{
get
{
return _spec2;
}
}
internal AndSpecification(ISpecification<TEntity> spec1, ISpecification<TEntity> spec2)
{
if (spec1 == null)
throw new ArgumentNullException("spec1");
if (spec2 == null)
throw new ArgumentNullException("spec2");
_spec1 = spec1;
_spec2 = spec2;
}
public bool IsSatisfiedBy(TEntity candidate)
{
return Spec1.IsSatisfiedBy(candidate) && Spec2.IsSatisfiedBy(candidate);
}
}
internal class OrSpecification<TEntity> : ISpecification<TEntity>
{
private readonly ISpecification<TEntity> _spec1;
private readonly ISpecification<TEntity> _spec2;
protected ISpecification<TEntity> Spec1
{
get
{
return _spec1;
}
}
protected ISpecification<TEntity> Spec2
{
get
{
return _spec2;
}
}
internal OrSpecification(ISpecification<TEntity> spec1, ISpecification<TEntity> spec2)
{
if (spec1 == null)
throw new ArgumentNullException("spec1");
if (spec2 == null)
throw new ArgumentNullException("spec2");
_spec1 = spec1;
_spec2 = spec2;
}
public bool IsSatisfiedBy(TEntity candidate)
{
return Spec1.IsSatisfiedBy(candidate) || Spec2.IsSatisfiedBy(candidate);
}
}
internal class NotSpecification<TEntity> : ISpecification<TEntity>
{
private readonly ISpecification<TEntity> _wrapped;
protected ISpecification<TEntity> Wrapped
{
get
{
return _wrapped;
}
}
internal NotSpecification(ISpecification<TEntity> spec)
{
if (spec == null)
{
throw new ArgumentNullException("spec");
}
_wrapped = spec;
}
public bool IsSatisfiedBy(TEntity candidate)
{
return !Wrapped.IsSatisfiedBy(candidate);
}
}
public static class ExtensionMethods
{
public static ISpecification<TEntity> And<TEntity>(this ISpecification<TEntity> spec1, ISpecification<TEntity> spec2)
{
return new AndSpecification<TEntity>(spec1, spec2);
}
public static ISpecification<TEntity> Or<TEntity>(this ISpecification<TEntity> spec1, ISpecification<TEntity> spec2)
{
return new OrSpecification<TEntity>(spec1, spec2);
}
public static ISpecification<TEntity> Not<TEntity>(this ISpecification<TEntity> spec)
{
return new NotSpecification<TEntity>(spec);
}
}
Пример использования
В следующем примере, мы проверяем счета и отсылаем их в агентство по сбору платежей, если: они просрочены, ещё не были отправлены в агентство и покупателю было выслано предупреждение. Этот пример показывает как правила «сцепляются» друг с другом.
Пример опирается на три спецификации: OverdueSpecification, которое верно, если счёт был выставлен более чем 30 дней назад, NoticeSentSpecification верно, если покупателю было отослано 3 предупреждения и InCollectionSpecification, проверяющая что счёт ещё не отсылался в агентство по сбору платежей. Реализация этих классов не так важна.
Используя эти три спецификации, мы создаём новое правило SendToCollection, которое верно, если выполняются все три условия, описанные в прошлом абзаце.
OverDueSpecification OverDue = new OverDueSpecification();
NoticeSentSpecification NoticeSent = new NoticeSentSpecification();
InCollectionSpecification InCollection = new InCollectionSpecification();
// пример "сцепления" правил
ISpecification<Invoice> SendToCollection = OverDue.And(NoticeSent).And(InCollection.Not());
InvoiceCollection = Service.GetInvoices();
foreach (Invoice currentInvoice in InvoiceCollection) {
if (SendToCollection.IsSatisfiedBy(currentInvoice)) {
currentInvoice.SendToCollection();
}
}
Примечания
Литература
- Evans, E: «Domain-Driven Design.», page 224. Addison-Wesley, 2004.
Ссылки
- Specifications by Eric Evans and Martin Fowler
- The Specification Pattern: A Primer by Matt Berther
- The Specification Pattern: A Four Part Introduction using VB.Net by Richard Dalton
- specification pattern in flash actionscript 3 by Rolf Vreijdenberger