Specification Pattern

Specification Pattern

vg

Das Specification Pattern ist ein Designmuster, das es ermöglicht, komplexe Geschäftslogik und Anforderungen in Form von Spezifikationen auszudrücken. Eine Spezifikation ist eine Bedingung oder Regel, die überprüft wird, um zu entscheiden, ob ein Objekt eine bestimmte Eigenschaft oder ein bestimmtes Verhalten hat. Das Muster erleichtert die Validierung und das Kombinieren von Anforderungen in einer flexiblen und wiederverwendbaren Weise.

In diesem Artikel wird das Specification Pattern näher beschrieben und anhand von Beispielen in C++ erläutert. Dabei werden auch die Vorteile und Nachteile dieses Musters diskutiert.

Funktionsweise des Specification Patterns

Das Specification Pattern wird oft verwendet, um Geschäftsregeln zu kapseln und sie von der restlichen Anwendung zu trennen. Es ermöglicht, dass die Regeln flexibel kombiniert und wiederverwendet werden. Eine Spezifikation stellt eine Bedingung dar, die auf Objekte angewendet wird, um zu prüfen, ob sie den Anforderungen entsprechen.

Eine Spezifikation kann mit anderen Spezifikationen kombiniert werden. Es gibt häufig zwei Möglichkeiten, dies zu tun:

  • AND-Verknüpfung: Zwei Spezifikationen müssen beide wahr sein, damit das gesamte Ergebnis wahr ist.
  • OR-Verknüpfung: Eine der Spezifikationen muss wahr sein, damit das gesamte Ergebnis wahr ist.

Das Specification Pattern nutzt meist eine abstrakte Spezifikationsklasse, die eine Methode zur Überprüfung der Bedingungen bereitstellt. Diese abstrakte Klasse wird dann in konkreten Spezifikationen weiter spezifiziert.

Beispiel in C++

In C++ kann das Specification Pattern durch Vererbung und das Erstellen von Spezifikationen als separate Klassen umgesetzt werden. Nachfolgend ein einfaches Beispiel, das das Pattern veranschaulicht.

#include <iostream>
#include <vector>
#include <functional>

// Abstrakte Spezifikation
template <typename T>
class Specification {
public:
    virtual bool isSatisfiedBy(const T& item) const = 0;
};

// Konkrete Spezifikation: Überprüft, ob ein Wert größer als 10 ist
class GreaterThanTenSpecification : public Specification<int> {
public:
    bool isSatisfiedBy(const int& item) const override {
        return item > 10;
    }
};

// Konkrete Spezifikation: Überprüft, ob ein Wert kleiner als 20 ist
class LessThanTwentySpecification : public Specification<int> {
public:
    bool isSatisfiedBy(const int& item) const override {
        return item < 20;
    }
};

// UND-Verknüpfung von Spezifikationen
template <typename T>
class AndSpecification : public Specification<T> {
private:
    const Specification<T>& spec1;
    const Specification<T>& spec2;
public:
    AndSpecification(const Specification<T>& spec1, const Specification<T>& spec2) : spec1(spec1), spec2(spec2) {}

    bool isSatisfiedBy(const T& item) const override {
        return spec1.isSatisfiedBy(item) && spec2.isSatisfiedBy(item);
    }
};

int main() {
    GreaterThanTenSpecification greaterThanTen;
    LessThanTwentySpecification lessThanTwenty;
    
    AndSpecification<int> combinedSpec(greaterThanTen, lessThanTwenty);

    std::vector<int> values = {5, 15, 25, 30};

    for (const int& value : values) {
        if (combinedSpec.isSatisfiedBy(value)) {
            std::cout << value << " satisfies the combined specification." << std::endl;
        } else {
            std::cout << value << " does not satisfy the combined specification." << std::endl;
        }
    }

    return 0;
}

In diesem Beispiel wird die Spezifikation in Form von zwei Klassen (GreaterThanTenSpecification und LessThanTwentySpecification) definiert, die eine Bedingung überprüfen. Dann wird mit der AndSpecification eine Kombination dieser Spezifikationen erstellt. Das Programm prüft, ob die Werte in einem Vektor beide Bedingungen erfüllen.

Beispiel des Specification Patterns in Python

Das Specification Pattern ist ein Entwurfsmuster, das verwendet wird, um Geschäftslogik zu kapseln und Kriterien zu definieren, die auf Objekte angewendet werden. Es ermöglicht, komplexe logische Ausdrücke für Objekte zu erstellen und diese miteinander zu kombinieren.

Hier ist ein einfaches Beispiel für das Specification Pattern in Python:

Beispiel

Angenommen, wir haben ein System, das Kunden basierend auf bestimmten Kriterien filtern muss, wie z.B. das Alter des Kunden oder ob er in einem bestimmten Land lebt.

from abc import ABC, abstractmethod

# Die Basisklasse für Spezifikationen
class Specification(ABC):
    @abstractmethod
    def is_satisfied_by(self, item):
        pass

    def and_spec(self, other_spec):
        return AndSpecification(self, other_spec)

    def or_spec(self, other_spec):
        return OrSpecification(self, other_spec)

    def not_spec(self):
        return NotSpecification(self)


# Konkrete Spezifikationen
class AgeSpecification(Specification):
    def __init__(self, min_age):
        self.min_age = min_age

    def is_satisfied_by(self, customer):
        return customer.age >= self.min_age


class CountrySpecification(Specification):
    def __init__(self, country):
        self.country = country

    def is_satisfied_by(self, customer):
        return customer.country == self.country


# Kombinierte Spezifikationen
class AndSpecification(Specification):
    def __init__(self, spec1, spec2):
        self.spec1 = spec1
        self.spec2 = spec2

    def is_satisfied_by(self, item):
        return self.spec1.is_satisfied_by(item) and self.spec2.is_satisfied_by(item)


class OrSpecification(Specification):
    def __init__(self, spec1, spec2):
        self.spec1 = spec1
        self.spec2 = spec2

    def is_satisfied_by(self, item):
        return self.spec1.is_satisfied_by(item) or self.spec2.is_satisfied_by(item)


class NotSpecification(Specification):
    def __init__(self, spec):
        self.spec = spec

    def is_satisfied_by(self, item):
        return not self.spec.is_satisfied_by(item)


# Beispiel eines Kunden
class Customer:
    def __init__(self, name, age, country):
        self.name = name
        self.age = age
        self.country = country


# Kundenliste
customers = [
    Customer("Alice", 30, "Germany"),
    Customer("Bob", 25, "USA"),
    Customer("Charlie", 35, "Germany"),
    Customer("David", 40, "USA"),
    Customer("Eva", 22, "Germany")
]

# Anwendung der Spezifikationen
age_spec = AgeSpecification(30)
country_spec = CountrySpecification("Germany")

# Filtere Kunden, die älter als 30 sind und in Deutschland leben
spec = age_spec.and_spec(country_spec)

# Ausgabe der Kunden, die die Spezifikation erfüllen
filtered_customers = [customer.name for customer in customers if spec.is_satisfied_by(customer)]
print(filtered_customers)

Erklärung

  • Specification (Basisklasse): Dies ist eine abstrakte Basisklasse für alle Spezifikationen, die eine Methode is_satisfied_by() erfordert. Diese Methode überprüft, ob ein Objekt (z. B. ein Kunde) die Spezifikation erfüllt.
  • Konkrete Spezifikationen (AgeSpecification, CountrySpecification): Diese Klassen implementieren das is_satisfied_by() und definieren Kriterien wie das Mindestalter und das Land.
  • Kombinierte Spezifikationen (AndSpecification, OrSpecification, NotSpecification): Diese Klassen erlauben es, Spezifikationen zu kombinieren, z.B. um Kunden zu filtern, die beide Kriterien (Alter und Land) erfüllen müssen.
  • Kundenbeispiel: Wir haben eine Liste von Kunden, und mithilfe von Spezifikationen können wir eine komplexe Filterlogik erstellen, um die richtigen Kunden zu finden.

Beispielausgabe:

Für das obige Beispiel (Kunden, die älter als 30 Jahre sind und in Deutschland leben):

['Charlie']

Dieses Beispiel zeigt, wie man mit dem Specification Pattern flexibler und modularer filternde Logik aufbauen kann, ohne die eigentliche Logik der Objekte zu verändern.

Vorteile des Specification Patterns

  1. Flexibilität und Wiederverwendbarkeit: Das Pattern ermöglicht es, Spezifikationen zu kombinieren, um komplexe Anforderungen zu definieren. Diese Spezifikationen können in verschiedenen Teilen des Systems wiederverwendet werden.
  2. Trennung von Geschäftslogik und Anwendungslogik: Durch das Kapseln von Geschäftsregeln in Spezifikationen wird der Code modular und übersichtlicher. Dies führt zu einer besseren Wartbarkeit.
  3. Erweiterbarkeit: Neue Spezifikationen können problemlos hinzugefügt werden, ohne dass die bestehende Struktur verändert werden muss. Die Spezifikationen können durch einfache Vererbung erweitert werden.
  4. Klarheit der Anforderungen: Das Pattern fördert eine klare Darstellung der Anforderungen in Form von Spezifikationen. Diese sind gut dokumentiert und leicht verständlich.
  5. Bessere Testbarkeit: Da Spezifikationen in isolierten Klassen implementiert sind, lassen sie sich gut testen. Man kann Unit-Tests für jede Spezifikation schreiben.

Nachteile des Specification Patterns

  1. Komplexität bei zu vielen Spezifikationen: Wenn viele Spezifikationen definiert werden, kann es schwierig werden, die Logik zu verstehen und zu warten. Dies könnte zu einer Erhöhung der Komplexität führen.
  2. Leistungseinbußen durch viele Kombinationen: Wenn viele Spezifikationen miteinander kombiniert werden, kann dies zu Leistungseinbußen führen, insbesondere in großen Systemen oder bei komplexen Prüfungen.
  3. Schwierigkeiten bei komplexen Anforderungen: Wenn die Anforderungen sehr komplex sind und viele spezialisierte Bedingungen erfordern, kann das Pattern dazu führen, dass die Struktur des Systems übermäßig fragmentiert wird.
  4. Erhöhte Anzahl an Klassen: Das Specification Pattern führt zu einer größeren Anzahl an Klassen, da jede Spezifikation in einer eigenen Klasse implementiert wird. Dadurch kann das System unnötig aufblähen.
  5. Potentielle Redundanz: In einigen Fällen könnten ähnliche Spezifikationen mehrfach definiert werden. Dies führt zu redundanten Codezeilen und könnte die Wartung erschweren.

Anwendung des Specification Patterns

Das Specification Pattern eignet sich besonders für Anwendungen, die eine Vielzahl von komplexen Geschäftsregeln oder Validierungsanforderungen erfordern. Beispiele für solche Anwendungen sind:

  • E-Commerce-Systeme: Ein System, das Produkte aufgrund von Benutzerkriterien filtert (z. B. Preis, Marke, Kategorie), kann Spezifikationen für jede Bedingung definieren.
  • Datenbankabfragen: In Systemen, die Datenbankabfragen dynamisch generieren, kann das Pattern verwendet werden, um flexibel Filter zu kombinieren.
  • Validierungssysteme: In Anwendungen, die Benutzereingaben validieren, können verschiedene Regeln für die Eingabe validiert werden, wie z. B. Mindestlänge, Format oder erlaubte Zeichen.

Wann sollte das Specification Pattern eingesetzt werden?

Das Specification Pattern sollte in Situationen eingesetzt werden, in denen du komplexe, wiederverwendbare und kombinierbare Bedingungen für Objekte definieren und anwenden möchtest. Hier sind einige Szenarien, in denen das Pattern besonders nützlich ist:

1. Komplexe Filterlogik

Wenn du eine komplexe Geschäftslogik hast, bei der Objekte mehrere, oft kombinierte Bedingungen erfüllen müssen, bietet das Specification Pattern eine elegante Möglichkeit, diese Logik zu kapseln und modular zu gestalten. Anstatt komplexe if-Bedingungen im Code zu haben, kannst du sie in spezialisierte Spezifikationen aufteilen und kombinieren.

Beispiel:

  • Kunden filtern nach verschiedenen Kriterien wie Alter, Standort, Einkommen, Aktivitätsstatus usw.
  • Produkte filtern nach mehreren Kriterien wie Preis, Verfügbarkeit und Bewertungen.

2. Wiederverwendbare Regeln und Bedingungen

Wenn du Regeln oder Bedingungen hast, die an mehreren Stellen im Code wiederverwendet werden sollen, ist das Specification Pattern hilfreich. Es erlaubt dir, diese Regeln einmal zu definieren und überall dort zu verwenden, wo sie benötigt werden, ohne sie zu duplizieren.

Beispiel:

  • Eine Regel, dass ein Kunde mindestens 18 Jahre alt sein muss, kann sowohl beim Registrierungsprozess als auch bei der Verifikation einer Bestellung angewendet werden.

3. Dynamische Zusammensetzung von Regeln

Das Specification Pattern ist ideal, wenn du Regeln dynamisch kombinieren musst, je nachdem, welche Bedingungen für einen bestimmten Anwendungsfall erforderlich sind. Du kannst Spezifikationen mit logischen Operatoren wie AND, OR, und NOT kombinieren, ohne dass du die zugrunde liegende Logik erneut implementieren musst.

Beispiel:

  • Ein Kunde könnte berechtigt sein, einen Rabatt zu erhalten, wenn er sowohl ein bestimmtes Alter als auch einen Mindestbetrag an Käufen überschreitet. Die beiden Regeln lassen sich leicht mit dem Pattern kombinieren.

4. Sauberer und wartbarer Code

Wenn du deinen Code sauber und wartbar halten möchtest, hilft das Specification Pattern dabei, logische Ausdrücke zu isolieren. Es vermeidet die Unordnung von langen und verschachtelten if-Anweisungen und sorgt dafür, dass die Geschäftslogik gut strukturiert und leichter verständlich bleibt.

Beispiel:

  • Anstatt an mehreren Stellen im Code dieselbe komplexe Bedingung zu wiederholen, kannst du eine Spezifikation erstellen und diese einfach auf Objekte anwenden. Das reduziert Redundanz und vereinfacht das Testen der Logik.

5. Testbarkeit

Spezifikationen sind leicht testbar, da jede einzelne Spezifikation eine isolierte Bedingung überprüft. Durch das Erstellen kleiner, spezialisierter Tests für jede Spezifikation kannst du sicherstellen, dass deine Geschäftslogik korrekt funktioniert.

Beispiel:

  • Du kannst Tests für einzelne Spezifikationen schreiben, z.B. sicherstellen, dass die AgeSpecification korrekt prüft, ob ein Kunde das Mindestalter erreicht hat, ohne sich um andere Teile des Codes kümmern zu müssen.

6. Trennung von Geschäftslogik und Domain-Logik

Das Specification Pattern hilft, die Geschäftslogik (z.B. Regeln und Kriterien) von der Domain-Logik (z.B. die Struktur der Objekte) zu trennen. So bleibt der Code sauberer und leichter wartbar.

Beispiel:

  • Die Geschäftslogik für das Validieren von Kundenkonditionen ist getrennt von der Logik zur Verarbeitung der Kundenbestellungen. So wird die Wartbarkeit und Erweiterbarkeit des Codes verbessert.

7. Verwendung in Entwurfsmustern wie Repository

In komplexen Systemen mit Repositories (z.B. bei der Arbeit mit Datenbanken) wird das Specification Pattern häufig verwendet, um dynamische Abfragen zu erstellen. Du kannst Spezifikationen zusammenstellen, um die gewünschte Datenabfrage zu formulieren, ohne den Repository-Code selbst ändern zu müssen.

Beispiel:

  • Ein Repository könnte eine Methode haben, die eine Liste von Spezifikationen akzeptiert, und dann alle Kunden zurückgibt, die alle Kriterien erfüllen.

Fazit

Das Specification Pattern ist ein leistungsstarkes Muster, das eine klare Trennung der Geschäftslogik von der Anwendung ermöglicht. Durch das Kapseln von Anforderungen in spezialisierte Klassen bietet es eine hohe Flexibilität und Wiederverwendbarkeit. Das Muster ist besonders hilfreich in Systemen mit komplexen und dynamischen Anforderungen, da neue Spezifikationen problemlos hinzugefügt und kombiniert werden können. Allerdings kann die Anzahl der Spezifikationen die Komplexität erhöhen und die Wartbarkeit erschweren, insbesondere in großen Systemen.

Zurück zur Pattern-Liste: Liste der Design-Pattern

com

Newsletter Anmeldung

Bleiben Sie informiert! Wir informieren Sie über alle neuen Beiträge (max. 1 Mail pro Woche – versprochen)