Model de strategie - Strategy pattern

În programarea computerizată , modelul de strategie (cunoscut și sub denumirea de model de politică ) este un model de proiectare software de comportament care permite selectarea unui algoritm în timpul rulării. În loc să implementeze direct un singur algoritm, codul primește instrucțiuni în timp de execuție cu privire la ce trebuie utilizate într-o familie de algoritmi.

Strategia permite algoritmului să varieze independent de clienții care îl utilizează. Strategia este unul dintre tiparele incluse în influenta carte Design Patterns de Gamma et al. care a popularizat conceptul de utilizare a modelelor de design pentru a descrie cum să proiectăm software flexibil și reutilizabil orientat obiect. Amânarea deciziei cu privire la algoritmul de utilizat până la runtime permite codului de apel să fie mai flexibil și reutilizabil.

De exemplu, o clasă care efectuează validarea datelor primite poate utiliza modelul de strategie pentru a selecta un algoritm de validare în funcție de tipul de date, sursa datelor, alegerea utilizatorului sau alți factori discriminanți. Acești factori nu sunt cunoscuți până la rulare și pot necesita o validare radical diferită. Algoritmii de validare (strategii), încapsulați separat de obiectul de validare, pot fi utilizați de alte obiecte de validare în diferite zone ale sistemului (sau chiar sisteme diferite) fără duplicarea codului .

De obicei, modelul de strategie stochează o referință la un anumit cod într-o structură de date și îl recuperează. Acest lucru poate fi realizat prin mecanisme precum indicatorul funcției native, funcția de primă clasă , clase sau instanțe de clasă în limbaje de programare orientate obiect sau accesând stocarea internă a codului prin implementarea limbajului prin reflecție .

Structura

Clasa UML și diagrama secvenței

Image
Un exemplu de clasă UML și diagramă de secvență pentru modelul de proiectare Strategie.

În diagrama de clasă UML de mai sus , Contextclasa nu implementează un algoritm direct. În schimb, Contextse referă la Strategyinterfața pentru efectuarea unui algoritm ( strategy.algorithm()), care face Contextindependent de modul în care este implementat un algoritm. Strategy1Și Strategy2clase pună în aplicare Strategyinterfață, care este, pune în aplicare (încapsulat) un algoritm. UML Diagrama de secvență arată interacțiunile run-time: The Delegatii obiect un algoritm pentru diferite obiecte. În primul rând, apelează la un obiect, care efectuează algoritmul și returnează rezultatul . Ulterior, își schimbă strategia și apelează la un obiect, care efectuează algoritmul și returnează rezultatul .
ContextStrategyContextalgorithm()Strategy1ContextContextalgorithm()Strategy2Context

Diagrama clasei

Image
Model de strategie în UML

Image
Model de strategie în LePUS3 ( legendă )

Exemplu

C #

Următorul exemplu este în 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

Următorul exemplu este în 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 și principiu deschis / închis

Image
Comportamentele de accelerare și de frânare trebuie declarate în fiecare model nou de mașină .

Conform tiparului de strategie, comportamentele unei clase nu trebuie moștenite. În schimb, acestea ar trebui încapsulate folosind interfețe. Acest lucru este compatibil cu principiul deschis / închis (OCP), care propune ca clasele să fie deschise pentru extensie, dar închise pentru modificare.

De exemplu, luați în considerare o clasă de mașini. Două funcționalități posibile pentru mașină sunt frânarea și accelerarea . Deoarece comportamentele de accelerare și frânare se schimbă frecvent între modele, o abordare comună este implementarea acestor comportamente în subclasele. Această abordare are dezavantaje semnificative: comportamentele de accelerare și de frânare trebuie declarate în fiecare nou model de mașină. Munca de gestionare a acestor comportamente crește foarte mult pe măsură ce numărul modelelor crește și necesită duplicarea codului între modele. În plus, nu este ușor să se determine natura exactă a comportamentului pentru fiecare model fără a investiga codul din fiecare.

Modelul de strategie folosește compoziția în loc de moștenire . În tiparul de strategie, comportamentele sunt definite ca interfețe separate și clase specifice care implementează aceste interfețe. Acest lucru permite o mai bună decuplare între comportament și clasa care folosește comportamentul. Comportamentul poate fi schimbat fără a sparge clasele care îl utilizează, iar clasele pot comuta între comportamente schimbând implementarea specifică utilizată fără a necesita modificări semnificative ale codului. Comportamentele pot fi, de asemenea, modificate în timpul rulării, precum și în timpul proiectării. De exemplu, comportamentul frânei unui obiect de mașină poate fi schimbat de la BrakeWithABS()la Brake()prin schimbarea brakeBehaviorelementului în:

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

Vezi si

Referințe

linkuri externe