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 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 objektum orientá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

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)

 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 

Programtervezési minták
Java-kódpéldák
C Sharp-kódpéldák
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 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 objektum orientá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 Mínusz class Minus \ : ICalculateInterface { public int Calculate( int value1 , int value2 ) { return value1 - value2 ; } } //Stratégia 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,ToString ( ) ) ; CalculateClient plusClient = new CalculateClient( new Plus ( ) ) ; Response.Write ( " <br / >Plus : " + plusClient.Calculate ( 7,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 = 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 = for( Double i \ : drinks ) { sum += i ; } return sum*0.9 ; } } Egy sokkal egyszerűbb példa a 'modern Javában' ( Javavagy újabb ) a lambdák használata 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 ; } } / * Kliens mely a fenti Bake algoritmust használja konstruktorban * / public class Sedan extends Car { public Sedan ( ) { this.brakeBehavior = new Brake ( ) ; } } / * 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 ) 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 Programtervezési minták Java-kódpéldák C Sharp-kódpéldák