Specifikáció programtervezési minta
A számítástudományban a specifikáció programtervezési minta egy olyan minta, amelynek üzleti szabályai kombinálhatók egymással a logika szabályai szerint. A mintát gyakran használják a tartományvezérelt fejlesztésben. Nem tévesztendő össze a program specifikációjával, ami azt írja le, hogy a programnak mit kell tudni.
A specifikáció tervminta üzleti szabályt vázol, ami kombinálható más üzleti szabályokkal. Ebben a mintában az üzleti logika egy egysége funkcionalitását az összetett specifikáció (Composite Specification) absztrakt aggregátumból örökli. Az összetett specifikációnak van egy IsSatisfiedBy logikai függvénye. Példányosítás után A specifikációt összeláncolják más specifikációkkal, így az új specifikációk könnyen karbantarthatók, de jól konfigurálhatók. Továbbá példányosítva metódushívással vagy a kontroll megfordításával megváltoztatja az állapotát, így egy másik osztály delegáltjává válik.
Kódpéldák
[szerkesztés]C#
[szerkesztés]Egy C# kódpélda:
public interface ISpecification
{
bool IsSatisfiedBy(object candidate);
ISpecification And(ISpecification other);
ISpecification AndNot(ISpecification other);
ISpecification Or(ISpecification other);
ISpecification OrNot(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 AndNot(ISpecification other)
{
return new AndNotSpecification(this, other);
}
public ISpecification Or(ISpecification other)
{
return new OrSpecification(this, other);
}
public ISpecification OrNot(ISpecification other)
{
return new OrNotSpecification(this, other);
}
public ISpecification Not()
{
return new NotSpecification(this);
}
}
public class AndSpecification : CompositeSpecification
{
private ISpecification leftCondition;
private ISpecification rightCondition;
public AndSpecification(ISpecification left, ISpecification right)
{
leftCondition = left;
rightCondition = right;
}
public override bool IsSatisfiedBy(object candidate)
{
return leftCondition.IsSatisfiedBy(candidate) && rightCondition.IsSatisfiedBy(candidate);
}
}
public class AndNotSpecification : CompositeSpecification
{
private ISpecification leftCondition;
private ISpecification rightCondition;
public AndNotSpecification(ISpecification left, ISpecification right)
{
leftCondition = left;
rightCondition = right;
}
public override bool IsSatisfiedBy(object candidate)
{
return leftCondition.IsSatisfiedBy(candidate) && rightCondition.IsSatisfiedBy(candidate) != true;
}
}
public class OrSpecification : CompositeSpecification
{
private ISpecification leftCondition;
private ISpecification rightCondition;
public OrSpecification(ISpecification left, ISpecification right)
{
leftCondition = left;
rightCondition = right;
}
public override bool IsSatisfiedBy(object candidate)
{
return leftCondition.IsSatisfiedBy(candidate) || rightCondition.IsSatisfiedBy(candidate);
}
}
public class OrNotSpecification : CompositeSpecification
{
private ISpecification leftCondition;
private ISpecification rightCondition;
public OrNotSpecification(ISpecification left, ISpecification right)
{
leftCondition = left;
rightCondition = right;
}
public override bool IsSatisfiedBy(object candidate)
{
return leftCondition.IsSatisfiedBy(candidate) || rightCondition.IsSatisfiedBy(candidate) != true;
}
}
public class NotSpecification : CompositeSpecification
{
private ISpecification Wrapped;
public NotSpecification(ISpecification x)
{
Wrapped = x;
}
public override bool IsSatisfiedBy(object candidate)
{
return !Wrapped.IsSatisfiedBy(candidate);
}
}
C# 6.0 generikusokkal
[szerkesztés] public interface ISpecification<T>
{
bool IsSatisfiedBy(T candidate);
ISpecification<T> And(ISpecification<T> other);
ISpecification<T> AndNot(ISpecification<T> other);
ISpecification<T> Or(ISpecification<T> other);
ISpecification<T> OrNot(ISpecification<T> other);
ISpecification<T> Not();
}
public abstract class LinqSpecification<T> : CompositeSpecification<T>
{
public abstract Expression<Func<T, bool>> AsExpression();
public override bool IsSatisfiedBy(T candidate) => AsExpression().Compile()(candidate);
}
public abstract class CompositeSpecification<T> : ISpecification<T>
{
public abstract bool IsSatisfiedBy(T candidate);
public ISpecification<T> And(ISpecification<T> other) => new AndSpecification<T>(this, other);
public ISpecification<T> AndNot(ISpecification<T> other) => new AndNotSpecification<T>(this, other);
public ISpecification<T> Or(ISpecification<T> other) => new OrSpecification<T>(this, other);
public ISpecification<T> OrNot(ISpecification<T> other) => new OrNotSpecification<T>(this, other);
public ISpecification<T> Not() => new NotSpecification<T>(this);
}
public class AndSpecification<T> : CompositeSpecification<T>
{
ISpecification<T> left;
ISpecification<T> right;
public AndSpecification(ISpecification<T> left, ISpecification<T> right)
{
this.left = left;
this.right = right;
}
public override bool IsSatisfiedBy(T candidate) => left.IsSatisfiedBy(candidate) && right.IsSatisfiedBy(candidate);
}
public class AndNotSpecification<T> : CompositeSpecification<T>
{
ISpecification<T> left;
ISpecification<T> right;
public AndNotSpecification(ISpecification<T> left, ISpecification<T> right)
{
this.left = left;
this.right = right;
}
public override bool IsSatisfiedBy(T candidate) => left.IsSatisfiedBy(candidate) && right.IsSatisfiedBy(candidate) != true;
}
public class OrSpecification<T> : CompositeSpecification<T>
{
ISpecification<T> left;
ISpecification<T> right;
public OrSpecification(ISpecification<T> left, ISpecification<T> right)
{
this.left = left;
this.right = right;
}
public override bool IsSatisfiedBy(T candidate) => left.IsSatisfiedBy(candidate) || right.IsSatisfiedBy(candidate);
}
public class OrNotSpecification<T> : CompositeSpecification<T>
{
ISpecification<T> left;
ISpecification<T> right;
public OrNotSpecification(ISpecification<T> left, ISpecification<T> right)
{
this.left = left;
this.right = right;
}
public override bool IsSatisfiedBy(T candidate) => left.IsSatisfiedBy(candidate) || right.IsSatisfiedBy(candidate) != true;
}
public class NotSpecification<T> : CompositeSpecification<T>
{
ISpecification<T> other;
public NotSpecification(ISpecification<T> other) => this.other = other;
public override bool IsSatisfiedBy(T candidate) => !other.IsSatisfiedBy(candidate);
}
Példa a használatra
[szerkesztés]A következő példában hívásokat küldünk a kollekció ügynökségnek, ha:
- a hívás esedékes
- az értesítések el lettek küldve
- nincsenek meg a kollekció ügynökségnél.
A példa a logika láncolásának végeredményét mutatja be.
A felhasználó feltételez egy már korábban használt OverdueSpecification osztályt, ami igazat mutat, ha a hívási dátum legalább 30 napos, egy NoticeSentSpecification osztályt, ami igazat mutat, ha három értesítést elküldtek, és egy InCollectionSpecification osztályt, ami igazat mutat, ha már küldték a hívást. Ezeknek az implementációja itt nem érdekes.
Ezekkel létrehozunk egy újabb specifikáció osztályt, aminek neve SendToCollection, ami akkor igaz, ha a fenti három feltétel teljesül.
var OverDue = new OverDueSpecification();
var NoticeSent = new NoticeSentSpecification();
var InCollection = new InCollectionSpecification();
// example of specification pattern logic chaining
var SendToCollection = OverDue.And(NoticeSent).And(InCollection.Not());
var InvoiceCollection = Service.GetInvoices();
foreach (var currentInvoice in InvoiceCollection) {
if (SendToCollection.IsSatisfiedBy(currentInvoice)) {
currentInvoice.SendToCollection();
}
}
Kritika
[szerkesztés]A specifikáció minta tekinthető programtervezési antimintának is:
- Cargo kultusz antiminta: A mintának nincsenek jó definiált céljai, hiányoznak az útmutatások is, hogy mikor használjuk és mikor ne. Lásd még: Az eszköz törvénye (arany kalapács).
- Belső platform hatás: A logikai függvények C#-ban duplikálják a már meglevő alsóbb szintű logikai műveleteket. Lásd még: A kerék újrafeltalálása.
- Spagetti kód antiminta: Mivel a minta a specifikáció minden részéhez más osztályt definiál, a kód töredezettségét okozza, mivel azt is szétbontja, ami összetartozik. A fenti példában az OverDue egy extra réteg a SendToCollection és az OverDueSpecification között.
A legtöbb programozási nyelv az alapvető objektumorientált fogalmakkal természetesen alkalmazkodik a tartományvezérelt fejlesztéshez.
A példa a specifikáció minta nélkül:
var InvoiceCollection = Service.GetInvoices();
foreach (var invoice in InvoiceCollection) invoice.SendToCollectionIfNecessary();
// Invoice methods:
public void SendToCollectionIfNecessary()
{
if (ShouldSendToCollection()) SendToCollection();
}
private bool ShouldSendToCollection() => currentInvoice.OverDue && currentInvoice.NoticeSent && !currentInvoice.InCollection;
Ez az alternatíva csak a következőket használja: csak olvasható tulajdonságok, feltétellogika és függvények. Itt akulcs alternatíva a csak olvasható tulajdonságok, amelyekl jól elnevezve támogatják a tartományvezérelt fejlesztést, és a specifikáció által definiált logikai műveletek helyett a nyelvben már meglevő logikai műveleteket használja. Továbbá ergy jól elnevezett SendToCollectionIfNecessary függvény potenciálisan hasznosabb és még leíró is, jobban, mint a specifikációt használó példa, ami tartalmazott ugyan egy hasonló függvényt, de az nem kapcsolódott közvetlenül az objektumhoz.
Források
[szerkesztés]- Evans, Eric. Domain Driven Design. Addison-Wesley, 224. o. (2004)
Fordítás
[szerkesztés]Ez a szócikk részben vagy egészben a Specification pattern című angol Wikipédia-szócikk fordításán alapul. Az eredeti cikk szerkesztőit annak laptörténete sorolja fel. Ez a jelzés csupán a megfogalmazás eredetét és a szerzői jogokat jelzi, nem szolgál a cikkben szereplő információk forrásmegjelöléseként.