CodeGym /Java blog /Véletlen /Stratégia tervezési minta
John Squirrels
Szint
San Francisco

Stratégia tervezési minta

Megjelent a csoportban
Szia! A mai leckében a stratégiai mintáról fogunk beszélni. Az előző leckékben már röviden megismerkedtünk az öröklődés fogalmával. Ha elfelejtette, emlékeztetem Önt, hogy ez a kifejezés egy általános programozási feladat szabványos megoldására vonatkozik. A CodeGymnél gyakran mondjuk, hogy szinte minden kérdésre megtalálhatod a választ a google-ban. Ez azért van így, mert az Ön feladatát, bármi legyen is az, valószínűleg valaki más már sikeresen megoldotta. A minták a leggyakoribb feladatok bevált megoldásai, vagy a problémás helyzetek megoldásának módszerei. Olyanok ezek, mint a "kerekek", amiket nem kell újra feltalálni, de tudni kell, hogyan és mikor kell használni :) A minták másik célja az egységes architektúra elősegítése. Valaki más kódjának elolvasása nem könnyű feladat! Mindenki más kódot ír, mert ugyanaz a feladat sokféleképpen megoldható. De a minták használata segít a különböző programozóknak megérteni a programozási logikát anélkül, hogy belemélyednének a kód minden sorába (még akkor is, ha először látjuk!) Ma az egyik leggyakoribb tervezési mintát, a "Stratégát" nézzük meg. Tervezési minta: Stratégia - 2Képzelje el, hogy olyan programot írunk, amely aktívan együttműködik a Conveyance objektumokkal. Teljesen mindegy, hogy pontosan mit csinál a programunk. Osztályhierarchiát hoztunk létre egy Conveyance szülőosztályból és három gyermekosztályból: Sedan , Truck és F1Car .

public class Conveyance {

   public void go() {
       System.out.println("Moving forward");
   }

   public void stop() {

       System.out.println("Braking!");
   }
}

public class Sedan extends Conveyance {
}

public class Truck extends Conveyance {
}

public class F1Car extends Conveyance {
}
Mindhárom gyermekosztály két szabványos metódust örököl a szülőtől: go() és stop() . A programunk nagyon egyszerű: autóink csak előrehaladva tudnak fékezni. Folytatva munkánkat, úgy döntöttünk, hogy egy új módszert adunk az autóknak: fill() (értsd: "töltsd meg a benzintartályt"). Hozzáadtuk a Conveyance szülőosztályhoz:

public class Conveyance {

   public void go() {
       System.out.println("Moving forward");
   }

   public void stop() {

       System.out.println("Braking!");
   }
  
   public void fill() {
       System.out.println("Refueling!");
   }
}
Valóban felmerülhetnek problémák egy ilyen egyszerű helyzetben? Sőt, már van... Tervezési minta: Stratégia - 3

public class Stroller extends Conveyance {

   public void fill() {
      
       // Hmm... This is a stroller for children. It doesn't need to be refueled :/
   }
}
A programunknak most van egy szállítóeszköze (babakocsi), ami nem illik jól az általános koncepcióba. Lehet benne pedál vagy rádióvezérlésű, de egy dolog biztos: nem lesz hová tölteni a benzint. Osztályhierarchiánk következtében az általános metódusokat olyan osztályok örökölték, amelyeknek nincs szükségük rájuk. Mit tegyünk ebben a helyzetben? Nos, felülírhatnánk a fill() metódust a Stroller osztályban, hogy ne történjen semmi, amikor megpróbálja tankolni a babakocsit:

public class Stroller extends Conveyance {

   @Override
   public void fill() {
       System.out.println("A stroller cannot be refueled!");
   }
}
De ez aligha nevezhető sikeres megoldásnak, ha más okból, mint a duplikált kód miatt. Például a legtöbb osztály a szülő osztály metódusát fogja használni, de a többiek kénytelenek felülírni azt. Ha 15 osztályunk van, és ezek közül 5-6-ban felül kell bírálnunk a viselkedést, akkor a kódduplikáció meglehetősen kiterjedt lesz. Talán az interfészek segíthetnek nekünk? Például így:

public interface Fillable {
  
   public void fill();
}
Létrehozunk egy kitölthető felületet egy fill() metódussal. Ekkor azok a járművek, amelyeket tankolni kell, megvalósítják ezt a felületet, míg a többi jármű (például a babakocsink) nem. De ez a lehetőség nem felel meg nekünk. A jövőben az osztályhierarchiánk nagyon nagyra nőhet (képzelje csak el, mennyi különféle típusú szállítmány létezik a világon). Elhagytuk a korábbi, öröklődő verziót, mert nem akarjuk felülbírálni a fill()módszer sokszor-sokszor. Most már minden osztályban meg kell valósítanunk! És mi van, ha 50-en vagyunk? És ha gyakori változtatások lesznek a programunkban (és ez szinte mindig igaz a valódi programokra!), akkor mind az 50 osztályt végig kell rohannunk, és mindegyik viselkedését manuálisan módosítanunk kell. Tehát végül mit tegyünk ebben a helyzetben? A problémánk megoldásához más utat választunk. Nevezetesen elválasztjuk osztályunk viselkedését magától az osztálytól. Az mit jelent? Mint tudják, minden objektumnak van állapota (adatkészlet) és viselkedése (metódusok halmaza). Szállítási osztályunk viselkedése három metódusból áll: go() , stop() és fill() . Az első két módszer úgy jó, ahogy van. De a harmadik módszert áthelyezzük aSzállítási osztály. Ez elválasztja a viselkedést az osztálytól (pontosabban csak a viselkedés egy részét választja el, mivel az első két metódus ott marad, ahol van). Tehát hova tegyük a fill() metódusunkat? Nem jut eszembe semmi :/ Úgy tűnik, pont ott van, ahol lennie kell. Egy külön felületre helyezzük át: FillStrategy !

public interface FillStrategy {

   public void fill();
}
Miért van szükségünk ilyen felületre? Minden egyértelmű. Most több osztályt is létrehozhatunk, amelyek megvalósítják ezt a felületet:

public class HybridFillStrategy implements FillStrategy {
  
   @Override
   public void fill() {
       System.out.println("Refuel with gas or electricity — your choice!");
   }
}

public class F1PitstopStrategy implements FillStrategy {
  
   @Override
   public void fill() {
       System.out.println("Refuel with gas only after all other pit stop procedures are complete!");
   }
}

public class StandardFillStrategy implements FillStrategy {
   @Override
   public void fill() {
       System.out.println("Just refuel with gas!");
   }
}
Három viselkedési stratégiát hoztunk létre: egyet a közönséges autókhoz, egyet a hibridekhez és egyet a Forma-1-es versenyautókhoz. Minden stratégia más tankolási algoritmust valósít meg. A mi esetünkben egyszerűen megjelenítünk egy karakterláncot a konzolon, de mindegyik metódus tartalmazhat összetett logikát. Mi lesz a következő, amit csinálunk?

public class Conveyance {

   FillStrategy fillStrategy;

   public void fill() {
       fillStrategy.fill();
   }

   public void go() {
       System.out.println("Moving forward");
   }

   public void stop() {
       System.out.println("Braking!");
   }
  
}
A FillStrategy felületünket a Conveyance szülőosztály mezőjeként használjuk . Vegye figyelembe, hogy nem egy konkrét megvalósítást jelölünk meg, hanem egy felületet használunk. Az autóosztályoknak a FillStrategy interfész speciális megvalósítására lesz szükségük:

public class F1Car extends Conveyance {

   public F1Car() {
       this.fillStrategy = new F1PitstopStrategy();
   }
}

public class HybridCar extends Conveyance {

   public HybridCar() {
       this.fillStrategy = new HybridFillStrategy();
   }
}

public class Sedan extends Conveyance {

   public Sedan() {
       this.fillStrategy = new StandardFillStrategy();
   }
}

Nézzük, mit kaptunk!

public class Main {

   public static void main(String[] args) {

       Conveyance sedan = new Sedan();
       Conveyance hybrid = new HybridCar();
       Conveyance f1car = new F1Car();

       sedan.fill();
       hybrid.fill();
       f1car.fill();
   }
}
Konzol kimenet:

Just refuel with gas! 
Refuel with gas or electricity — your choice! 
Refuel with gas only after all other pit stop procedures are complete!
Nagy! A tankolás folyamata úgy működik, ahogy kell! Amúgy semmi akadálya annak, hogy a stratégiát paraméterként használjuk a konstruktorban! Például így:

public class Conveyance {

   private FillStrategy fillStrategy;

   public Conveyance(FillStrategy fillStrategy) {
       this.fillStrategy = fillStrategy;
   }

   public void fill() {
       this.fillStrategy.fill();
   }

   public void go() {
       System.out.println("Moving forward");
   }

   public void stop() {
       System.out.println("Braking!");
   }
}

public class Sedan extends Conveyance {

   public Sedan() {
       super(new StandardFillStrategy());
   }
}



public class HybridCar extends Conveyance {

   public HybridCar() {
       super(new HybridFillStrategy());
   }
}

public class F1Car extends Conveyance {

   public F1Car() {
       super(new F1PitstopStrategy());
   }
}
Futtassuk a main() metódusunkat (amely változatlan marad). Ugyanazt az eredményt kapjuk! Konzol kimenet:

Just refuel with gas! 
Refuel with gas or electricity — your choice! 
Refuel with gas only after all other pit stop procedures are complete!
A stratégiai tervezési minta meghatározza az algoritmusok családját, mindegyiket magába foglalja, és biztosítja, hogy felcserélhetők legyenek. Lehetővé teszi az algoritmusok módosítását, függetlenül attól, hogy az ügyfél hogyan használja őket (ez a "Head First Design Patterns" című könyvből vett definíció számomra kiválónak tűnik). Tervezési minta: Stratégia - 4Az általunk érdekelt algoritmuscsaládot (autók tankolásának módjai) már külön felületeken, különböző implementációkkal határoztuk meg. Elválasztottuk őket magától az autótól. Ha módosítanunk kell egy tankolási algoritmust, az semmilyen módon nem fogja befolyásolni az autóosztályainkat. A felcserélhetőség eléréséhez pedig csak egyetlen setter metódust kell hozzáadnunk a Conveyance osztályunkhoz:

public class Conveyance {

   FillStrategy fillStrategy;

   public void fill() {
       fillStrategy.fill();
   }

   public void go() {
       System.out.println("Moving forward");
   }

   public void stop() {
       System.out.println("Braking!");
   }

   public void setFillStrategy(FillStrategy fillStrategy) {
       this.fillStrategy = fillStrategy;
   }
}
Most már menet közben is változtathatunk stratégiákon:

public class Main {

   public static void main(String[] args) {

       Stroller stroller= new Stroller();
       stroller.setFillStrategy(new StandardFillStrategy());

       stroller.fill();
   }
}
Ha a babakocsik hirtelen benzinnel kezdenek működni, programunk készen áll a forgatókönyv kezelésére :) És ennyi! Megtanult még egy tervezési mintát, amely kétségtelenül nélkülözhetetlen és hasznos lesz, ha valódi projekteken dolgozik :) A következő alkalomig!
Hozzászólások
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION