Strategimønster - Strategy pattern

I computerprogrammering er strategimønsteret (også kendt som politikmønsteret ) et adfærdsmæssigt softwaredesignmønster, der gør det muligt at vælge en algoritme ved runtime. I stedet for at implementere en enkelt algoritme direkte, modtager kode køre-tid instruktioner om, hvilken i en familie af algoritmer der skal bruges.

Strategi lader algoritmen variere uafhængigt af klienter, der bruger den. Strategi er et af de mønstre, der indgår i den indflydelsesrige bog Design Patterns af Gamma et al. der populariserede konceptet med at bruge designmønstre til at beskrive, hvordan man designer fleksibel og genanvendelig objektorienteret software. Udskyde beslutningen om, hvilken algoritme der skal bruges indtil runtime, gør det muligt for opkaldskoden at være mere fleksibel og genanvendelig.

For eksempel kan en klasse, der udfører validering af indgående data, bruge strategimønsteret til at vælge en valideringsalgoritme afhængigt af datatypen, datakilden, brugervalg eller andre diskriminerende faktorer. Disse faktorer kendes ikke før driftstid og kan kræve radikalt anderledes validering. Valideringsalgoritmerne (strategier), indkapslet adskilt fra det validerende objekt, kan bruges af andre validerende objekter i forskellige områder af systemet (eller endda forskellige systemer) uden kodeduplikation .

Strategimønsteret gemmer typisk en reference til en vis kode i en datastruktur og henter den. Dette kan opnås ved hjælp af mekanismer som den native funktionsmarkør , førsteklasses funktion , klasser eller klasseforekomster i objektorienterede programmeringssprog eller adgang til sprogimplementeringens interne lagring af kode via refleksion .

Struktur

UML klasse og sekvensdiagram

Et eksempel på et UML -klasse- og sekvensdiagram for strategidesignmønsteret.

I den ovennævnte UML klasse diagram , det Contextbetyder klasse ikke implementere en algoritme direkte. I stedet Contexthenviser til Strategygrænsefladen til udførelse af en algoritme ( strategy.algorithm()), som gør Contextuafhængig af, hvordan en algoritme implementeres. Den Strategy1og Strategy2klasser implementere Strategygrænsefladen, dvs. implementere (indkapslede) en algoritme.
Den UML sekvensdiagram viser runtime-interaktioner: De Contextobjekt delegerer en algoritme til forskellige Strategyobjekter. Først Contextkalder algorithm()på et Strategy1objekt, som udfører algoritmen og returnerer resultatet til Context. Derefter Contextændrer sin strategi og kalder algorithm()på et Strategy2objekt, som udfører algoritmen og returnerer resultatet til Context.

Klassediagram

Strategimønster i UML

Strategimønster i LePUS3 ( legende )

Eksempel

C#

Følgende eksempel er i 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

Følgende eksempel er i 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();
    }
}

Strategi og åbent/lukket princip

Accelerere og bremse adfærd skal angives i hver ny bilmodel .

Ifølge strategimønsteret bør en klasses adfærd ikke arves. I stedet skal de indkapsles ved hjælp af grænseflader. Dette er kompatibelt med princippet om åben/lukket (OCP), som foreslår, at klasser skal være åbne for udvidelse, men lukkede for ændringer.

Som et eksempel kan du overveje en bilklasse. To mulige funktioner til bilen er bremse og acceleration . Da acceleration og bremseadfærd ofte ændres mellem modeller, er en fælles fremgangsmåde at implementere denne adfærd i underklasser. Denne tilgang har betydelige ulemper: acceleration og bremseadfærd skal angives i hver ny bilmodel. Arbejdet med at styre denne adfærd stiger kraftigt, efterhånden som antallet af modeller stiger, og kræver, at kode kopieres på tværs af modeller. Derudover er det ikke let at bestemme den nøjagtige karakter af adfærden for hver model uden at undersøge koden i hver.

Strategimønsteret anvender sammensætning i stedet for arv . I strategimønsteret defineres adfærd som separate grænseflader og specifikke klasser, der implementerer disse grænseflader. Dette tillader bedre afkobling mellem adfærden og den klasse, der bruger adfærden. Adfærden kan ændres uden at bryde de klasser, der bruger den, og klasserne kan skifte mellem adfærd ved at ændre den specifikke implementering, der bruges uden at kræve nogen væsentlige kodeændringer. Adfærd kan også ændres i løbetid såvel som på designtidspunktet. For eksempel kan en bilobjekts bremseadfærd ændres fra BrakeWithABS()til Brake()ved at ændre brakeBehaviorelementet til:

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"
    }
}

Se også

Referencer

eksterne links