CodeGym /Java blog /Tilfældig /Strategi design mønster
John Squirrels
Niveau
San Francisco

Strategi design mønster

Udgivet i gruppen
Hej! I dagens lektion vil vi tale om strategimønster. I tidligere lektioner har vi allerede kort stiftet bekendtskab med begrebet arv. Hvis du har glemt det, vil jeg minde dig om, at dette udtryk refererer til en standardløsning til en almindelig programmeringsopgave. Hos CodeGym siger vi ofte, at du kan google svaret på næsten alle spørgsmål. Det skyldes, at din opgave, uanset hvad den er, sandsynligvis allerede er blevet løst med succes af en anden. Mønstre er gennemprøvede løsninger på de mest almindelige opgaver eller metoder til at løse problematiske situationer. Disse er ligesom "hjul", som du ikke behøver at genopfinde på egen hånd, men du skal vide, hvordan og hvornår du skal bruge dem :) Et andet formål med mønstre er at fremme ensartet arkitektur. At læse en andens kode er ingen nem opgave! Alle skriver forskellig kode, fordi den samme opgave kan løses på mange måder. Men brugen af ​​mønstre hjælper forskellige programmører med at forstå programmeringslogikken uden at dykke ned i hver linje kode (selv når man ser den for første gang!) I dag ser vi på et af de mest almindelige designmønstre kaldet "Strategi". Designmønster: Strategi - 2Forestil dig, at vi skriver et program, der aktivt vil arbejde med transportobjekter. Det er lige meget, hvad vores program præcist gør. Vi har oprettet et klassehierarki med én overførselsklasse og tre børneklasser: Sedan , lastbil og 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 {
}
Alle tre underordnede klasser arver to standardmetoder fra forælderen: go() og stop() . Vores program er meget enkelt: vores biler kan kun køre fremad og aktivere bremserne. For at fortsætte vores arbejde besluttede vi at give bilerne en ny metode: fill() (betyder "fyld benzintanken"). Vi føjede det til overførselsforælderklassen :

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 der virkelig opstå problemer i så simpel en situation? Faktisk har de allerede... 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 :/
   }
}
Vores program har nu et transportmiddel (en barnevogn), der ikke passer godt ind i det generelle koncept. Den kunne have pedaler eller være radiostyret, men én ting er sikker - den vil ikke have noget sted at hælde gas i. Vores klassehierarki har bevirket, at almindelige metoder er nedarvet af klasser, der ikke har brug for dem. Hvad skal vi gøre i denne situation? Nå, vi kunne tilsidesætte fill()- metoden i Stroller- klassen, så der ikke sker noget, når du forsøger at tanke klapvognen:

public class Stroller extends Conveyance {

   @Override
   public void fill() {
       System.out.println("A stroller cannot be refueled!");
   }
}
Men dette kan næppe kaldes en vellykket løsning, hvis det ikke er af anden grund end duplikatkode. For eksempel vil de fleste klasser bruge den overordnede klasses metode, men resten vil blive tvunget til at tilsidesætte den. Hvis vi har 15 klasser, og vi skal tilsidesætte adfærd i 5-6 af dem, vil kodeduplikeringen blive ret omfattende. Måske kan grænseflader hjælpe os? For eksempel sådan her:

public interface Fillable {
  
   public void fill();
}
Vi opretter en Fyldbar grænseflade med én fill() metode. Så vil de transportmidler, der skal tankes, implementere denne grænseflade, mens andre transportmidler (for eksempel vores barnevogn) ikke vil. Men denne mulighed passer ikke os. I fremtiden kan vores klassehierarki vokse til at blive meget stort (forestil dig bare, hvor mange forskellige typer transportmidler, der er i verden). Vi forlod den tidligere version, der involverede arv, fordi vi ikke ønsker at tilsidesætte fill()metode mange, mange gange. Nu skal vi implementere det i hver klasse! Og hvad hvis vi har 50? Og hvis der vil blive foretaget hyppige ændringer i vores program (og dette er næsten altid sandt for rigtige programmer!), ville vi være nødt til at haste gennem alle 50 klasser og manuelt ændre adfærden for hver af dem. Så hvad skal vi i sidste ende gøre i denne situation? For at løse vores problem vælger vi en anden måde. Vi vil nemlig adskille vores klasses adfærd fra klassen selv. Hvad betyder det? Som du ved, har hvert objekt tilstand (et sæt data) og adfærd (et sæt metoder). Vores transportklasses adfærd består af tre metoder: go() , stop() og fill() . De to første metoder er fine, ligesom de er. Men vi vil flytte den tredje metode ud afTransport klasse. Dette vil adskille adfærden fra klassen (mere præcist vil den kun adskille en del af adfærden, da de to første metoder forbliver, hvor de er). Så hvor skal vi placere vores fill()- metode? Intet kommer til at tænke på :/ Det virker som om det er præcis hvor det skal være. Vi flytter det til en separat grænseflade: FillStrategy !

public interface FillStrategy {

   public void fill();
}
Hvorfor har vi brug for sådan en grænseflade? Det hele er ligetil. Nu kan vi oprette flere klasser, der implementerer denne grænseflade:

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 skabte tre adfærdsstrategier: en for almindelige biler, en for hybrider og en for Formel 1 racerbiler. Hver strategi implementerer en anden optankningsalgoritme. I vores tilfælde viser vi blot en streng på konsollen, men hver metode kan indeholde en kompleks logik. Hvad gør vi så?

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 bruger vores FillStrategy- grænseflade som et felt i Conveyance- forælderklassen. Bemærk, at vi ikke angiver en specifik implementering – vi bruger en grænseflade. Bilklasserne har brug for specifikke implementeringer af FillStrategy- grænsefladen:

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

Lad os se på, hvad vi har!

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

Just refuel with gas! 
Refuel with gas or electricity — your choice! 
Refuel with gas only after all other pit stop procedures are complete!
Store! Tankningsprocessen fungerer som den skal! Intet forhindrer os i øvrigt i at bruge strategien som parameter i konstruktøren! For eksempel sådan her:

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());
   }
}
Lad os køre vores main() -metode (som forbliver uændret). Vi får samme resultat! Konsoludgang:

Just refuel with gas! 
Refuel with gas or electricity — your choice! 
Refuel with gas only after all other pit stop procedures are complete!
Strategidesignmønsteret definerer en familie af algoritmer, indkapsler hver af dem og sikrer, at de er udskiftelige. Det lader dig ændre algoritmerne, uanset hvordan de bruges af klienten (denne definition, taget fra bogen "Head First Design Patterns", forekommer mig fremragende). Designmønster: Strategi - 4Vi har allerede specificeret familien af ​​algoritmer, vi er interesserede i (måder at tanke biler på) i separate grænseflader med forskellige implementeringer. Vi adskilte dem fra selve bilen. Hvis vi nu skal foretage ændringer i en bestemt tankningsalgoritme, vil det ikke påvirke vores bilklasser på nogen måde. Og for at opnå udskiftelighed skal vi blot tilføje en enkelt setter-metode til vores transportklasse :

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 ændre strategier i farten:

public class Main {

   public static void main(String[] args) {

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

       stroller.fill();
   }
}
Hvis barnevogne pludselig begynder at køre på benzin, vil vores program være klar til at håndtere dette scenarie :) Og det er det hele! Du har lært endnu et designmønster, som uden tvivl vil være essentielt og nyttigt, når du arbejder på rigtige projekter :) Indtil næste gang!
Kommentarer
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION