Wzór strategii - Strategy pattern
W programowaniu komputerowym The pattern strategia (znany również jako wzór polityki ) jest behawioralny wzorzec projektowy , który umożliwia wybranie algorytmu przy starcie. Zamiast bezpośrednio implementować pojedynczy algorytm, kod otrzymuje instrukcje w czasie wykonywania, które w rodzinie algorytmów mają być użyte.
Strategia pozwala algorytmowi różnić się niezależnie od klientów, którzy go używają. Strategia jest jednym z wzorców zawartych we wpływowej książce Design Patterns autorstwa Gamma et al. który spopularyzował koncepcję wykorzystania wzorców projektowych do opisania sposobu projektowania elastycznego i wielokrotnego użytku oprogramowania obiektowego. Odroczenie decyzji o tym, którego algorytmu użyć do czasu uruchomienia, pozwala na większą elastyczność i ponowne użycie kodu wywołującego.
Na przykład klasa, która przeprowadza walidację danych przychodzących, może użyć wzorca strategii do wyboru algorytmu walidacji w zależności od typu danych, źródła danych, wyboru użytkownika lub innych czynników dyskryminujących. Czynniki te nie są znane do czasu uruchomienia i mogą wymagać przeprowadzenia radykalnie innej walidacji. Algorytmy (strategie) walidacji, hermetyzowane oddzielnie od obiektu walidującego, mogą być wykorzystywane przez inne obiekty walidujące w różnych obszarach systemu (lub nawet w różnych systemach) bez powielania kodu .
Zazwyczaj wzorzec strategii przechowuje odwołanie do kodu w strukturze danych i pobiera go. Można to osiągnąć poprzez takie mechanizmy, jak natywnego wskaźnika funkcji , z funkcją pierwszej klasy , klas lub instancji klasy w programowaniu obiektowym językach lub dostępu do pamięci wewnętrznej wdrożenia językowej kodu poprzez odbicie .
Struktura
Diagram klas i sekwencji UML
W powyższym UML schemacie klasy The Context
klasa nie implementuje algorytm bezpośrednio. Zamiast tego Context
odnosi się do Strategy
interfejsu do wykonywania algorytmu ( strategy.algorithm()
), który Context
uniezależnia sposób implementacji algorytmu. Strategy1
I Strategy2
klasy wdrożenia Strategy
interfejsu, czyli przyrząd (kapsułkowania) algorytm.
UML schemat sekwencji
przedstawia interakcje wykonywania czasu: obiektów delegatów algorytm do różnych obiektów. Po pierwsze, rozmowy na obiekcie, który wykonuje algorytm i zwraca wynik do . Następnie zmienia swoją strategię i połączeń na obiekcie, który wykonuje algorytm i zwraca wynik .
Context
Strategy
Context
algorithm()
Strategy1
Context
Context
algorithm()
Strategy2
Context
Diagram klas
Przykład
C#
Poniższy przykład jest w 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;
}
Jawa
Poniższy przykład jest w Javie .
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();
}
}
Strategia i zasada otwarte/zamknięte
Zgodnie ze wzorcem strategii zachowania klasy nie powinny być dziedziczone. Zamiast tego powinny być hermetyzowane za pomocą interfejsów. Jest to zgodne z zasadą open/closed (OCP), która proponuje, aby klasy były otwarte na rozszerzenie, ale zamknięte na modyfikację.
Jako przykład rozważ klasę samochodu. Dwie możliwe funkcje samochodu to hamowanie i przyspieszanie . Ponieważ zachowania przyspieszania i hamowania często zmieniają się między modelami, powszechnym podejściem jest implementacja tych zachowań w podklasach. Takie podejście ma istotne wady: w każdym nowym modelu samochodu należy zadeklarować zachowanie przyspieszenia i hamowania. Praca związana z zarządzaniem tymi zachowaniami znacznie wzrasta wraz ze wzrostem liczby modeli i wymaga duplikowania kodu w różnych modelach. Ponadto nie jest łatwo określić dokładną naturę zachowania dla każdego modelu bez zbadania kodu w każdym z nich.
Wzorzec strategii wykorzystuje kompozycję zamiast dziedziczenia . We wzorcu strategii zachowania są zdefiniowane jako oddzielne interfejsy i określone klasy, które implementują te interfejsy. Pozwala to na lepsze rozdzielenie między zachowaniem a klasą, która używa tego zachowania. Zachowanie można zmienić bez przerywania klas, które go używają, a klasy mogą przełączać się między zachowaniami, zmieniając konkretną używaną implementację bez konieczności wprowadzania znaczących zmian w kodzie. Zachowania można również zmieniać w czasie wykonywania, a także w czasie projektowania. Na przykład zachowanie hamulca obiektu samochodu można zmienić z BrakeWithABS()
na Brake()
przez zmianę brakeBehavior
elementu na:
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"
}
}
Zobacz też
- Wstrzykiwanie zależności
- Funkcja wyższego rzędu
- Lista terminów programowania obiektowego
- Mieszanie
- Projektowanie oparte na zasadach
- Typ klasy
- Jednostka-komponent-system
- Skład nad dziedziczeniem
Bibliografia
Linki zewnętrzne
- Wzorzec strategii w UML (po hiszpańsku)
- Geary, David (26 kwietnia 2002). „Strategia sukcesu” . Wzorce projektowe Java. JavaŚwiat . Źródło 2020-07-20 .
- Wzorzec strategii dla artykułu w języku C
- Refaktoryzacja: zamień kod typu na stan/strategia
- Wzorzec projektowy Strategy w Wayback Machine (archiwum 15.04.2017) Implementacja wzorca Strategy w JavaScript