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

[szerkesztés]
Stratégia tervezési minta UML ábrája
Stratégia tervezési minta LePUS3-ban (legend)

Példa

[szerkesztés]
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]

Stratégia és a nyílt-zárt alapelv

[szerkesztés]
Gyorsulás és fék viselkedések minden egyes új auto modellben deklarálva vannak.

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

[szerkesztés]
Commons:Category:Strategy (design pattern)
A Wikimédia Commons tartalmaz Stratégia programtervezési minta témájú médiaállományokat.
  1. Eric Freeman, Elisabeth Freeman, Kathy Sierra and Bert Bates, Head First Design Patterns, First Edition, Chapter 1, Page 24, O'Reilly Media, Inc, 2004. ISBN 978-0-596-00712-6
  2. Wikikönyvek: Computer Science Design Patterns/Strategy

További információk

[szerkesztés]
Az angol Wikikönyvekben
további információk találhatók

Fordítás

[szerkeszté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.