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ó programtervezési minta UML-osztálydiagramja

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

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.