Strategiemuster - Strategy pattern

In der Computerprogrammierung ist das Strategiemuster (auch als Richtlinienmuster bekannt ) ein verhaltensbezogenes Software-Entwurfsmuster, das die Auswahl eines Algorithmus zur Laufzeit ermöglicht. Anstatt einen einzelnen Algorithmus direkt zu implementieren, erhält der Code Laufzeitanweisungen, welche in einer Familie von Algorithmen verwendet werden sollen.

Strategy lässt den Algorithmus unabhängig von den Clients variieren, die ihn verwenden. Strategie ist eines der Muster, die in dem einflussreichen Buch Design Patterns von Gamma et al. die das Konzept der Verwendung von Entwurfsmustern populär gemacht hat, um zu beschreiben, wie flexible und wiederverwendbare objektorientierte Software entworfen wird. Wenn die Entscheidung über den zu verwendenden Algorithmus bis zur Laufzeit verschoben wird, kann der aufrufende Code flexibler und wiederverwendbar sein.

Beispielsweise kann eine Klasse, die eine Validierung an eingehenden Daten durchführt, das Strategiemuster verwenden, um einen Validierungsalgorithmus in Abhängigkeit vom Datentyp, der Datenquelle, der Benutzerauswahl oder anderen Unterscheidungsfaktoren auszuwählen. Diese Faktoren sind bis zur Laufzeit nicht bekannt und können die Durchführung einer radikal anderen Validierung erfordern. Die Validierungsalgorithmen (Strategien), die getrennt vom Validierungsobjekt gekapselt sind, können von anderen Validierungsobjekten in anderen Bereichen des Systems (oder sogar unterschiedlichen Systemen) ohne Codeduplizierung verwendet werden .

Normalerweise speichert das Strategiemuster einen Verweis auf einen Code in einer Datenstruktur und ruft ihn ab. Dies kann durch Mechanismen wie den nativen Funktionszeiger , die erstklassige Funktion , Klassen oder Klasseninstanzen in objektorientierten Programmiersprachen oder den Zugriff auf den internen Codespeicher der Sprachimplementierung über Reflektion erreicht werden .

Struktur

UML-Klassen- und Sequenzdiagramm

Ein Beispiel für ein UML-Klassen- und Sequenzdiagramm für das Strategie-Entwurfsmuster.

Im obigen UML -KlassendiagrammContext implementiert die Klasse keinen Algorithmus direkt. ContextBezieht sich stattdessen auf die StrategySchnittstelle zum Ausführen eines Algorithmus ( strategy.algorithm()), die Contextunabhängig davon macht, wie ein Algorithmus implementiert ist. Die Klassen Strategy1und Strategy2implementieren die StrategySchnittstelle, dh implementieren (kapseln) einen Algorithmus.
Das UML- Sequenzdiagramm zeigt die Laufzeitinteraktionen: Das ContextObjekt delegiert einen Algorithmus an verschiedene StrategyObjekte. Zuerst ContextAnrufe algorithm()auf einem Strategy1Objekt, das den Algorithmus und gibt das Ergebnis führt Context. Danach Contextändert er seine Strategie und ruft algorithm()ein Strategy2Objekt auf, das den Algorithmus ausführt und das Ergebnis an zurückgibt Context.

Klassen Diagramm

Strategiemuster in UML

Strategiemuster in LePUS3 ( Legende )

Beispiel

C#

Das folgende Beispiel ist in C# .

public class StrategyPatternWiki
{
    public static void Main(String[] args)
    {
        // Prepare strategies
        var normalStrategy    = new NormalStrategy();
        var happyHourStrategy = new HappyHourStrategy();

        var firstCustomer = new CustomerBill(normalStrategy);

        // Normal billing
        firstCustomer.Add(1.0, 1);

        // Start Happy Hour
        firstCustomer.Strategy = happyHourStrategy;
        firstCustomer.Add(1.0, 2);

        // New Customer
        var secondCustomer = new CustomerBill(happyHourStrategy);
        secondCustomer.Add(0.8, 1);
        // The Customer pays
        firstCustomer.Print();

        // End Happy Hour
        secondCustomer.Strategy = normalStrategy;
        secondCustomer.Add(1.3, 2);
        secondCustomer.Add(2.5, 1);
        secondCustomer.Print();
    }
}

// CustomerBill as class name since it narrowly pertains to a customer's bill
class CustomerBill
{
    private IList<double> drinks;

    // Get/Set Strategy
    public IBillingStrategy Strategy { get; set; }

    public CustomerBill(IBillingStrategy strategy)
    {
        this.drinks = new List<double>();
        this.Strategy = strategy;
    }

    public void Add(double price, int quantity)
    {
        this.drinks.Add(this.Strategy.GetActPrice(price * quantity));
    }

    // Payment of bill
    public void Print()
    {
        double sum = 0;
        foreach (var drinkCost in this.drinks)
        {
            sum += drinkCost;
        }
        Console.WriteLine($"Total due: {sum}.");
        this.drinks.Clear();
    }
}

interface IBillingStrategy
{
    double GetActPrice(double rawPrice);
}

// Normal billing strategy (unchanged price)
class NormalStrategy : IBillingStrategy
{
    public double GetActPrice(double rawPrice) => rawPrice;
}

// Strategy for Happy hour (50% discount)
class HappyHourStrategy : IBillingStrategy
{
    public double GetActPrice(double rawPrice) => rawPrice * 0.5;
}

Java

Das folgende Beispiel ist in Java .

import java.util.ArrayList;
import java.util.List;

interface BillingStrategy {
    // Use a price in cents to avoid floating point round-off error
    int getActPrice(int rawPrice);
  
    // Normal billing strategy (unchanged price)
    static BillingStrategy normalStrategy() {
        return rawPrice -> rawPrice;
    }
  
    // Strategy for Happy hour (50% discount)
    static BillingStrategy happyHourStrategy() {
        return rawPrice -> rawPrice / 2;
    }
}

class CustomerBill {
    private final List<Integer> drinks = new ArrayList<>();
    private BillingStrategy strategy;

    public CustomerBill(BillingStrategy strategy) {
        this.strategy = strategy;
    }

    public void add(int price, int quantity) {
        this.drinks.add(this.strategy.getActPrice(price*quantity));
    }

    // Payment of bill
    public void print() {
        int sum = this.drinks.stream().mapToInt(v -> v).sum();
        System.out.println("Total due: " + sum);
        this.drinks.clear();
    }

    // Set Strategy
    public void setStrategy(BillingStrategy strategy) {
        this.strategy = strategy;
    }
}

public class StrategyPattern {
    public static void main(String[] arguments) {
        // Prepare strategies
        BillingStrategy normalStrategy    = BillingStrategy.normalStrategy();
        BillingStrategy happyHourStrategy = BillingStrategy.happyHourStrategy();

        CustomerBill firstCustomer = new CustomerBill(normalStrategy);

        // Normal billing
        firstCustomer.add(100, 1);

        // Start Happy Hour
        firstCustomer.setStrategy(happyHourStrategy);
        firstCustomer.add(100, 2);

        // New Customer
        CustomerBill secondCustomer = new CustomerBill(happyHourStrategy);
        secondCustomer.add(80, 1);
        // The Customer pays
        firstCustomer.print();

        // End Happy Hour
        secondCustomer.setStrategy(normalStrategy);
        secondCustomer.add(130, 2);
        secondCustomer.add(250, 1);
        secondCustomer.print();
    }
}

Strategie und Open/Closed-Prinzip

Beschleunigungs- und Bremsverhalten müssen in jedem neuen Automodell angegeben werden .

Gemäß dem Strategiemuster sollten die Verhaltensweisen einer Klasse nicht vererbt werden. Stattdessen sollten sie über Schnittstellen gekapselt werden. Dies ist mit dem Open/Closed-Prinzip (OCP) kompatibel , das vorschlägt, dass Klassen offen für Erweiterungen, aber geschlossen für Modifikationen sein sollten.

Betrachten Sie als Beispiel eine Autoklasse. Zwei mögliche Funktionalitäten für das Auto sind Bremsen und Beschleunigen . Da sich das Beschleunigungs- und Bremsverhalten zwischen den Modellen häufig ändert, besteht ein üblicher Ansatz darin, dieses Verhalten in Unterklassen zu implementieren. Dieser Ansatz hat erhebliche Nachteile: Beschleunigungs- und Bremsverhalten müssen in jedem neuen Automodell angegeben werden. Der Aufwand für die Verwaltung dieser Verhaltensweisen nimmt mit zunehmender Anzahl von Modellen stark zu und erfordert, dass Code modellübergreifend dupliziert wird. Darüber hinaus ist es nicht einfach, die genaue Art des Verhaltens für jedes Modell zu bestimmen, ohne den Code in jedem zu untersuchen.

Das Strategiemuster verwendet Komposition anstelle von Vererbung . Im Strategiemuster werden Verhaltensweisen als separate Schnittstellen und spezifische Klassen definiert, die diese Schnittstellen implementieren. Dies ermöglicht eine bessere Entkopplung zwischen dem Verhalten und der Klasse, die das Verhalten verwendet. Das Verhalten kann geändert werden, ohne die Klassen zu unterbrechen, die es verwenden, und die Klassen können zwischen Verhalten wechseln, indem sie die spezifische verwendete Implementierung ändern, ohne dass wesentliche Codeänderungen erforderlich sind. Das Verhalten kann sowohl zur Laufzeit als auch zur Entwurfszeit geändert werden. Zum Beispiel kann das Bremsverhalten eines Autoobjekts von auf geändert werden BrakeWithABS(), Brake()indem das Element geändert wird brakeBehaviorin:

brakeBehavior = new Brake();
/* Encapsulated family of Algorithms
 * Interface and its implementations
 */
public interface IBrakeBehavior {
    public void brake();
}

public class BrakeWithABS implements IBrakeBehavior {
    public void brake() {
        System.out.println("Brake with ABS applied");
    }
}

public class Brake implements IBrakeBehavior {
    public void brake() {
        System.out.println("Simple Brake applied");
    }
}

/* Client that can use the algorithms above interchangeably */
public abstract class Car {
    private IBrakeBehavior brakeBehavior;

    public Car(IBrakeBehavior brakeBehavior) {
      this.brakeBehavior = brakeBehavior;
    }

    public void applyBrake() {
        brakeBehavior.brake();
    }

    public void setBrakeBehavior(IBrakeBehavior brakeType) {
        this.brakeBehavior = brakeType;
    }
}

/* Client 1 uses one algorithm (Brake) in the constructor */
public class Sedan extends Car {
    public Sedan() {
        super(new Brake());
    }
}

/* Client 2 uses another algorithm (BrakeWithABS) in the constructor */
public class SUV extends Car {
    public SUV() {
        super(new BrakeWithABS());
    }
}

/* Using the Car example */
public class CarExample {
    public static void main(final String[] arguments) {
        Car sedanCar = new Sedan();
        sedanCar.applyBrake();  // This will invoke class "Brake"

        Car suvCar = new SUV();
        suvCar.applyBrake();    // This will invoke class "BrakeWithABS"

        // set brake behavior dynamically
        suvCar.setBrakeBehavior( new Brake() );
        suvCar.applyBrake();    // This will invoke class "Brake"
    }
}

Siehe auch

Verweise

Externe Links