Stratégia programtervezési minta
A számítógép-programozásban a stratégia minta (vezérelv mintaként is ismert) egy szoftvertervezési minta, amely lehetővé teszi, hogy egy algoritmus viselkedését a futás során válasszuk meg. A stratégia minta meghatározza az algoritmusok egy családját, egységbe foglal minden algoritmust, és a családon belül cserélhetővé teszi ezeket az algoritmusokat. A stratégia segítségével az algoritmus az őt használó kliensektől függetlenül változhat, miután az megtette a beállításokat. A stratégia minta az egyike a Design Patterns könyvben[1] szereplő mintáknak, mely népszerűsítette a minták használatát a szoftvertervezés folyamatában.
Például egy osztály, mely bejövő adatokat érvényesít, stratégiai mintát használhat a validációs algoritmus kiválasztására a bejövő adat típusa, az adat forrása, felhasználói választás vagy más megkülönböztető tényező alapján. Ezek a tényezők nem minden esetben ismertek a futtatás előtt, és szükségessé válhat radikálisan eltérő érvényesítési módok használata. A validációs stratégiákat, melyeket külön egységbe zártunk a validáló objektumoktól, a rendszer más területéről származó validációs objektumok (vagy akár más rendszerek) is használhatják kódduplikáció nélkül.
A programozási nyelvekben alapvető követelmény, hogy képes legyen az adatstruktúrában található kódra mutató referenciák tárolására és elérésére. Ennek elérését olyan mechanizmusok segítik, mint a natív függvény pointerek, elsődleges típusú függvények, osztályok vagy osztály példányok az objektumorientált programozási nyelvekben, vagy mint a nyelv implementációs belső tárolójának elérése önelemzés (reflexió, reflection) segítségével.
Felépítése
Példa
C#
namespace IVSR.Designpattern.Strategy { //A stratégiák interfésze public interface ICalculateInterface { int Calculate(int value1, int value2); } //stratégiák //Stratégia 1: Mínusz class Minus : ICalculateInterface { public int Calculate(int value1, int value2) { return value1 - value2; } } //Stratégia 2: Plusz class Plus : ICalculateInterface { public int Calculate(int value1, int value2) { return value1 + value2; } } //A kliens class CalculateClient { private ICalculateInterface calculateStrategy; //Konstruktor: az interfészhez rendeli a stratégiá public CalculateClient(ICalculateInterface strategy) { this.calculateStrategy = strategy; } //Stratégia végrehajtása public int Calculate(int value1, int value2) { return calculateStrategy.Calculate(value1, value2); } } //Inicializálás protected void Page_Load(object sender, EventArgs e) { CalculateClient minusClient = new CalculateClient(new Minus()); Response.Write("<br />Minus: " + minusClient.Calculate(7, 1).ToString()); CalculateClient plusClient = new CalculateClient(new Plus()); Response.Write("<br />Plus: " + plusClient.Calculate(7, 1).ToString()); } }
Java
A következő példa Java programozási nyelvben íródott.
package javaapplication8; import java.util.ArrayList; import java.util.List; // Minta a Stratégia tervezési mintára public class JavaApplication8 { public static void main(String[] args){ Customer a = new Customer(); a.setStrategy(new NormalStrategy()); // Normál számlázás a.add(1.2,1); // Happy Hour inditása a.setStrategy(new HappyHourStrategy()); a.add(1.2,2); // Új ügyfél Customer b = new Customer(); b.setStrategy(new HappyHourStrategy()); b.add(0.8,1); // Az ügyfél fizet a.printBill(); // Happy Hour vége b.setStrategy(new NormalStrategy()); b.add(1.3,2); b.add(2.5,1); b.printBill(); } } class Customer{ private List<Double> drinks; private BillingStrategy strategy; public Customer() { this.drinks = new ArrayList<Double>(); } // Új elem az étlapra public void add(double price, int quantity){ drinks.add(price*quantity); } // Számla kifizetése public void printBill(){ System.out.println("Total due: " + strategy.sum(drinks)); drinks.clear(); } // Stratégia beállítása public void setStrategy(BillingStrategy strategy) { this.strategy = strategy; } } interface BillingStrategy{ // A fogyasztott italok összegzett ára public double sum(List<Double> drinks); } // Normál számlátási stratégia (változatlan árakon) class NormalStrategy implements BillingStrategy{ public double sum(List<Double> drinks) { double sum = 0; for(Double i : drinks){ sum += i; } return sum; } } // Happy hour stratégia(10% engedmény) class HappyHourStrategy implements BillingStrategy{ public double sum(List<Double> drinks) { double sum = 0; for(Double i : drinks){ sum += i; } return sum*0.9; } }
Egy sokkal egyszerűbb példa a 'modern Javában' (Java 8 vagy újabb) a lambdák használata[2]
Stratégia és a nyílt-zárt alapelv
A stratégia minta szerint az osztály viselkedését nem szabad örökítenünk. Ehelyett egységbe kell zárnunk interfészek alkalmazásával. Például vegyünk egy Autó osztályt. Az Autó két lehetséges funkciója a fék és a gyorsulás.
Mivel a gyorsulás és a fék viselkedése gyakran eltérő a különböző modellek esetén, az általános felfogás szerint ezeket a viselkedéseket külön osztályokba sorolnánk, amelyek ugyanazt az interfészt valósítják meg, a kód öröklésének kiiktatásával. Ennek a megközelítésnek viszont jelentős hátrányai vannak: minden egyes új autómodellhez újra kell értelmezni a gáz és a fék viselkedését. Ezen viselkedések kezelésével járó munka a modellek számának a növekedésével jelentősen megnő, és kód-duplikációhoz vezethet. Továbbá nem egyszerű feladat meghatározni minden modell pontos viselkedését anélkül, hogy a bennük megírt kódot külön megvizsgálnunk.
A stratégia minta öröklés helyett kompozíciót használ. Itt a viselkedést különálló interfészként és specifikus osztályokként definiáljuk, melyek ezeket az interfészeket valósítják meg. Ez lehetővé teszi a viselkedés és a viselkedést alkalmazó osztály jobb szétválasztását. A viselkedést anélkül tudjuk megváltoztatni, hogy betörnénk az osztályt, ami használja, illetve az osztályok lecserélhetik viselkedésüket a konkrét megvalósítás megváltoztatása által, anélkül hogy a kódot jelentőségteljesen megváltoztatnánk. A viselkedés változhat futási időben és tervezési időben is. Például az autó objektum fék viselkedését megváltoztathatjuk BrakeWithABS()
-ről Brake()
-re azáltal, hogy megváltoztatjuk a brakeBehavior
tagot az alábbi módon:
brakeBehavior = new Brake();
/* Algoritmusok egységbezárt családja * Interfész és annak megvalósításai */ 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"); } } /* Kliens mely a fenti algoritmusokat váltakozva használhatja */ public abstract class Car { protected IBrakeBehavior brakeBehavior; public void applyBrake() { brakeBehavior.brake(); } public void setBrakeBehavior(IBrakeBehavior brakeType) { this.brakeBehavior = brakeType; } } /* 1. Kliens mely a fenti Bake algoritmust használja konstruktorban */ public class Sedan extends Car { public Sedan() { this.brakeBehavior = new Brake(); } } /* 2. Kliens mely a BrakeWithABS algoritmust használja konstruktorban */ public class SUV extends Car { public SUV() { this.brakeBehavior = new BrakeWithABS(); } } /* Használunk egy PéldaAutót (carExample) */ public class CarExample { public static void main(String[] args) { Car sedanCar = new Sedan(); sedanCar.applyBrake(); // "Brake" osztály meghívása Car suvCar = new SUV(); suvCar.applyBrake(); // "BrakeWithABS" osztály meghívása // fék viselkedésének dinamikus beallítása suvCar.setBrakeBehavior( new Brake() ); suvCar.applyBrake(); // "Brake" osztály meghívása } }
Ez a megoldás nagyobb rugalmasságot biztosít és összhangban van a Nyitva-Zárt Alapelvvel, mely kimondja, hogy az osztályoknak nyitottnak kell lenni a kiterjesztésekre, de zártnak a módosításokra.
Források
További információk
- Strategy Pattern in UML (Spanish, but english model)
további információk találhatók
- The Strategy Pattern from the Net Objectives Repository
- Strategy Pattern for Java article
- Strategy Pattern for CSharp article
- Strategy pattern in UML and in LePUS3 (a formal modelling notation)
- Refactoring: Replace Type Code with State/Strategy
Fordítás
Ez a szócikk részben vagy egészben a Strategy pattern című angol Wikipédia-szócikk ezen változatának fordításán alapul. Az eredeti cikk szerkesztőit annak laptörténete sorolja fel. Ez a jelzés csupán a megfogalmazás eredetét és a szerzői jogokat jelzi, nem szolgál a cikkben szereplő információk forrásmegjelöléseként.