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
C#
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
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
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
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
- Evans, Eric. Domain Driven Design. Addison-Wesley, 224. o. (2004)
Fordítá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.