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
Im obigen UML -KlassendiagrammContext
implementiert die Klasse keinen Algorithmus direkt. Context
Bezieht sich stattdessen auf die Strategy
Schnittstelle zum Ausführen eines Algorithmus ( strategy.algorithm()
), die Context
unabhängig davon macht, wie ein Algorithmus implementiert ist. Die Klassen Strategy1
und Strategy2
implementieren die Strategy
Schnittstelle, dh implementieren (kapseln) einen Algorithmus.
Das UML- Sequenzdiagramm
zeigt die Laufzeitinteraktionen: Das Context
Objekt delegiert einen Algorithmus an verschiedene Strategy
Objekte. Zuerst Context
Anrufe algorithm()
auf einem Strategy1
Objekt, das den Algorithmus und gibt das Ergebnis führt Context
. Danach Context
ändert er seine Strategie und ruft algorithm()
ein Strategy2
Objekt auf, das den Algorithmus ausführt und das Ergebnis an zurückgibt Context
.
Klassen Diagramm
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
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 brakeBehavior
in:
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
- Abhängigkeitsspritze
- Funktion höherer Ordnung
- Liste objektorientierter Programmierbegriffe
- Mischen
- Richtlinienbasiertes Design
- Typklasse
- Entität–Komponenten–System
- Zusammensetzung über Vererbung
Verweise
Externe Links
- Strategiemuster in UML (auf Spanisch)
- Geary, David (26. April 2002). "Strategie zum Erfolg" . Java-Entwurfsmuster. JavaWelt . Abgerufen 2020-07-20 .
- Strategiemuster für C-Artikel
- Refactoring: Typcode durch Zustand/Strategie ersetzen
- Das Strategy Design Pattern an der Wayback Machine (archiviert 15.04.2017) Implementierung des Strategy Pattern in JavaScript