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.
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());
}
}
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]
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.
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.