CodeGym /Java blogg /Slumpmässig /Strategidesignmönster
John Squirrels
Nivå
San Francisco

Strategidesignmönster

Publicerad i gruppen
Hej! I dagens lektion kommer vi att prata om Strategimönster. I tidigare lektioner har vi redan kort bekantat oss med begreppet arv. Om du glömde, ska jag påminna dig om att denna term hänvisar till en standardlösning för en vanlig programmeringsuppgift. På CodeGym säger vi ofta att du kan googla svaret på nästan vilken fråga som helst. Detta beror på att din uppgift, vad den än är, förmodligen redan har lösts framgångsrikt av någon annan. Mönster är beprövade lösningar på de vanligaste uppgifterna, eller metoder för att lösa problematiska situationer. Dessa är som "hjul" som du inte behöver uppfinna om på egen hand, men du behöver veta hur och när du ska använda dem :) Ett annat syfte med mönster är att främja enhetlig arkitektur. Att läsa någon annans kod är ingen lätt uppgift! Alla skriver olika kod, eftersom samma uppgift kan lösas på många sätt. Men användningen av mönster hjälper olika programmerare att förstå programmeringslogiken utan att fördjupa sig i varje kodrad (även när man ser den för första gången!) Idag tittar vi på ett av de vanligaste designmönstren som kallas "Strategi". Designmönster: Strategi - 2Föreställ dig att vi skriver ett program som aktivt kommer att arbeta med transportobjekt. Det spelar ingen roll exakt vad vårt program gör. Vi har skapat en klasshierarki med en föräldraklass för transport och tre barnklasser: Sedan , Lastbil och 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 {
}
Alla tre underordnade klasser ärver två standardmetoder från föräldern: go() och stop() . Vårt program är väldigt enkelt: våra bilar kan bara köra framåt och bromsa. Vi fortsatte vårt arbete och bestämde oss för att ge bilarna en ny metod: fill() (vilket betyder "fyll bensintanken"). Vi har lagt till det i klassen Conveyance- förälder:

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!");
   }
}
Kan det verkligen uppstå problem i en så enkel situation? Faktum är att de redan har... Designmönster: Strategi - 3

public class Stroller extends Conveyance {

   public void fill() {
      
       // Hmm... This is a stroller for children. It doesn't need to be refueled :/
   }
}
Vårt program har nu ett transportmedel (en barnvagn) som inte passar bra in i det allmänna konceptet. Den kan ha pedaler eller vara radiostyrd, men en sak är säker - den kommer inte att ha någon plats att hälla i gas. Vår klasshierarki har gjort att vanliga metoder ärvs av klasser som inte behöver dem. Vad ska vi göra i den här situationen? Tja, vi skulle kunna åsidosätta fill() -metoden i klassen Stroller så att inget händer när du försöker tanka vagnen:

public class Stroller extends Conveyance {

   @Override
   public void fill() {
       System.out.println("A stroller cannot be refueled!");
   }
}
Men detta kan knappast kallas en lyckad lösning om inte av någon annan anledning än dubblettkod. Till exempel kommer de flesta av klasserna att använda den överordnade klassens metod, men resten kommer att tvingas åsidosätta den. Om vi ​​har 15 klasser och vi måste åsidosätta beteendet i 5-6 av dem kommer koddupliceringen att bli ganska omfattande. Kanske kan gränssnitt hjälpa oss? Till exempel, så här:

public interface Fillable {
  
   public void fill();
}
Vi skapar ett ifyllbart gränssnitt med metoden en fill() . Sedan kommer de transportmedel som behöver tankas att implementera detta gränssnitt, medan andra transportmedel (till exempel vår barnvagn) inte kommer att göra det. Men det här alternativet passar oss inte. I framtiden kan vår klasshierarki växa till att bli väldigt stor (föreställ dig bara hur många olika typer av transportmedel det finns i världen). Vi övergav den tidigare versionen som involverade arv, eftersom vi inte vill åsidosätta fill()metod många, många gånger. Nu måste vi implementera det i varje klass! Och tänk om vi har 50? Och om frekventa ändringar kommer att göras i vårt program (och detta är nästan alltid sant för riktiga program!), måste vi skynda oss igenom alla 50 klasser och manuellt ändra beteendet för var och en av dem. Så vad ska vi i slutändan göra i den här situationen? För att lösa vårt problem väljer vi ett annat sätt. Vi kommer nämligen att separera vår klasss beteende från själva klassen. Vad betyder det? Som ni vet har varje objekt tillstånd (en uppsättning data) och beteende (en uppsättning metoder). Vår överföringsklasss beteende består av tre metoder: go() , stop() och fill() . De två första metoderna är bra precis som de är. Men vi kommer att flytta ut den tredje metodenTransportklass . Detta kommer att separera beteendet från klassen (mer exakt, det kommer bara att separera en del av beteendet, eftersom de två första metoderna kommer att förbli där de är). Så var ska vi lägga vår fill()- metod? Ingenting kommer att tänka på :/ Det verkar som att det är precis där det ska vara. Vi flyttar det till ett separat gränssnitt: FillStrategy !

public interface FillStrategy {

   public void fill();
}
Varför behöver vi ett sådant gränssnitt? Det hela är okomplicerat. Nu kan vi skapa flera klasser som implementerar detta gränssnitt:

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!");
   }
}
Vi skapade tre beteendestrategier: en för vanliga bilar, en för hybrider och en för Formel 1-racerbilar. Varje strategi implementerar en annan tankningsalgoritm. I vårt fall visar vi helt enkelt en sträng på konsolen, men varje metod kan innehålla komplex logik. Vad gör vi härnäst?

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!");
   }
  
}
Vi använder vårt FillStrategy- gränssnitt som ett fält i förälderklassen Conveyance . Observera att vi inte anger en specifik implementering – vi använder ett gränssnitt. Bilklasserna kommer att behöva specifika implementeringar av FillStrategy- gränssnittet:

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

Låt oss titta på vad vi fick!

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();
   }
}
Konsolutgång:

Just refuel with gas! 
Refuel with gas or electricity — your choice! 
Refuel with gas only after all other pit stop procedures are complete!
Bra! Tankningsprocessen fungerar som den ska! Förresten, ingenting hindrar oss från att använda strategin som en parameter i konstruktorn! Till exempel, så här:

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());
   }
}
Låt oss köra vår main() -metod (som förblir oförändrad). Vi får samma resultat! Konsolutgång:

Just refuel with gas! 
Refuel with gas or electricity — your choice! 
Refuel with gas only after all other pit stop procedures are complete!
Strategins designmönster definierar en familj av algoritmer, kapslar in var och en av dem och säkerställer att de är utbytbara. Det låter dig modifiera algoritmerna oavsett hur de används av klienten (denna definition, hämtad från boken "Head First Design Patterns", verkar utmärkt för mig). Designmönster: Strategi - 4Vi har redan specificerat familjen av algoritmer vi är intresserade av (sätt att tanka bilar) i separata gränssnitt med olika implementeringar. Vi skiljde dem från själva bilen. Om vi ​​nu behöver göra några ändringar i en viss tankningsalgoritm kommer det inte att påverka våra bilklasser på något sätt. Och för att uppnå utbytbarhet behöver vi bara lägga till en enda sättermetod till vår transportklass :

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;
   }
}
Nu kan vi ändra strategier i farten:

public class Main {

   public static void main(String[] args) {

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

       stroller.fill();
   }
}
Om barnvagnar plötsligt börjar köras på bensin, kommer vårt program att vara redo att hantera detta scenario :) Och det är allt! Du har lärt dig ytterligare ett designmönster som utan tvekan kommer att vara viktigt och användbart när du arbetar med riktiga projekt :) Tills nästa gång!
Kommentarer
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION