CodeGym /Blog Java /Aleatoriu /Model de proiectare a strategiei
John Squirrels
Nivel
San Francisco

Model de proiectare a strategiei

Publicat în grup
Bună! În lecția de astăzi, vom vorbi despre modelul de strategie. În lecțiile anterioare, ne-am familiarizat deja pe scurt cu conceptul de moștenire. În cazul în care ați uitat, vă reamintesc că acest termen se referă la o soluție standard pentru o sarcină comună de programare. La CodeGym, spunem adesea că puteți căuta pe google răspunsul la aproape orice întrebare. Acest lucru se datorează faptului că sarcina ta, oricare ar fi ea, probabil că a fost deja rezolvată cu succes de altcineva. Modelele sunt soluții încercate și adevărate pentru cele mai comune sarcini sau metode de rezolvare a situațiilor problematice. Acestea sunt ca niște „roți” pe care nu ai nevoie să le reinventezi singur, dar trebuie să știi cum și când să le folosești :) Un alt scop al tiparelor este promovarea arhitecturii uniforme. Citirea codului altcuiva nu este o sarcină ușoară! Fiecare scrie cod diferit, deoarece aceeași sarcină poate fi rezolvată în multe feluri. Dar utilizarea modelelor îi ajută pe diferiți programatori să înțeleagă logica de programare fără să se adâncească în fiecare linie de cod (chiar și atunci când o vedem pentru prima dată!) Astăzi ne uităm la unul dintre cele mai comune modele de design numit „Strategie”. Model de design: Strategie - 2Imaginați-vă că scriem un program care va funcționa activ cu obiecte de transport. Nu contează exact ce face programul nostru. Am creat o ierarhie de clasă cu o clasă părinte de transport și trei clase secundare: Sedan , Truck și 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 {
}
Toate cele trei clase copil moștenesc două metode standard de la părinte: go() și stop() . Programul nostru este foarte simplu: mașinile noastre pot doar să avanseze și să aplice frânele. Continuând munca noastră, am decis să oferim mașinilor o nouă metodă: fill() (adică „umple rezervorul de benzină”). L-am adăugat la clasa părinte Conveyance :

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!");
   }
}
Pot să apară probleme într-adevăr într-o situație atât de simplă? De fapt, au deja... Model de design: Strategie - 3

public class Stroller extends Conveyance {

   public void fill() {
      
       // Hmm... This is a stroller for children. It doesn't need to be refueled :/
   }
}
Programul nostru are acum un vehicul (un cărucior pentru copii) care nu se încadrează bine în conceptul general. Ar putea avea pedale sau poate fi controlat prin radio, dar un lucru este sigur - nu va avea unde să toarnă gaz. Ierarhia noastră de clasă a făcut ca metodele comune să fie moștenite de clasele care nu au nevoie de ele. Ce ar trebui să facem în această situație? Ei bine, am putea suprascrie metoda fill() din clasa Stroller , astfel încât să nu se întâmple nimic atunci când încercați să alimentați căruciorul:

public class Stroller extends Conveyance {

   @Override
   public void fill() {
       System.out.println("A stroller cannot be refueled!");
   }
}
Dar aceasta cu greu poate fi numită o soluție de succes dacă nu există niciun alt motiv decât codul duplicat. De exemplu, majoritatea claselor vor folosi metoda clasei părinte, dar restul vor fi forțate să o înlocuiască. Dacă avem 15 clase și trebuie să suprascriem comportamentul în 5-6 dintre ele, duplicarea codului va deveni destul de extinsă. Poate interfețele ne pot ajuta? De exemplu, așa:

public interface Fillable {
  
   public void fill();
}
Vom crea o interfață Fillable cu o singură metodă fill() . Apoi, acele mijloace de transport care trebuie alimentate vor implementa această interfață, în timp ce alte mijloace de transport (de exemplu, căruciorul nostru pentru copii) nu o vor implementa. Dar această opțiune nu ne convine. În viitor, ierarhia noastră de clasă poate deveni foarte mare (doar imaginați-vă câte tipuri diferite de mijloace de transport există în lume). Am abandonat versiunea anterioară care implică moștenire, deoarece nu vrem să suprascriem fill()metoda de multe, de multe ori. Acum trebuie să-l implementăm în fiecare clasă! Și dacă avem 50? Și dacă se vor face modificări frecvente în programul nostru (și acest lucru este aproape întotdeauna adevărat pentru programele reale!), ar trebui să ne grăbim prin toate cele 50 de clase și să schimbăm manual comportamentul fiecăreia dintre ele. Deci, până la urmă, ce ar trebui să facem în această situație? Pentru a ne rezolva problema, vom alege un alt mod. Și anume, vom separa comportamentul clasei noastre de clasa în sine. Ce înseamnă asta? După cum știți, fiecare obiect are stare (un set de date) și comportament (un set de metode). Comportamentul clasei noastre de transport constă din trei metode: go() , stop() și fill() . Primele două metode sunt bune așa cum sunt. Dar vom muta a treia metodă dinClasa de transport . Acest lucru va separa comportamentul de clasă (mai precis, va separa doar o parte din comportament, deoarece primele două metode vor rămâne acolo unde sunt). Deci, unde ar trebui să punem metoda noastră fill() ? Nu-mi vine nimic în minte :/ Se pare că este exact unde ar trebui să fie. Îl vom muta într-o interfață separată: FillStrategy !

public interface FillStrategy {

   public void fill();
}
De ce avem nevoie de o astfel de interfață? Totul este simplu. Acum putem crea mai multe clase care implementează această interfață:

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!");
   }
}
Am creat trei strategii comportamentale: una pentru mașinile obișnuite, una pentru hibride și una pentru mașinile de curse de Formula 1. Fiecare strategie implementează un algoritm de realimentare diferit. În cazul nostru, pur și simplu afișăm un șir pe consolă, dar fiecare metodă poate conține o logică complexă. Ce facem mai departe?

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!");
   }
  
}
Folosim interfața FillStrategy ca câmp în clasa părinte Conveyance . Rețineți că nu indicăm o implementare specifică - folosim o interfață. Clasele de mașini vor avea nevoie de implementări specifice ale interfeței FillStrategy :

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

Să ne uităm la ce avem!

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();
   }
}
Ieșire din consolă:

Just refuel with gas! 
Refuel with gas or electricity — your choice! 
Refuel with gas only after all other pit stop procedures are complete!
Grozav! Procesul de realimentare funcționează așa cum trebuie! Apropo, nimic nu ne împiedică să folosim strategia ca parametru în constructor! De exemplu, așa:

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());
   }
}
Să rulăm metoda noastră main() (care rămâne neschimbată). Obtinem acelasi rezultat! Ieșire din consolă:

Just refuel with gas! 
Refuel with gas or electricity — your choice! 
Refuel with gas only after all other pit stop procedures are complete!
Modelul de proiectare a strategiei definește o familie de algoritmi, încapsulează fiecare dintre ei și asigură interschimbarea lor. Vă permite să modificați algoritmii indiferent de modul în care aceștia sunt utilizați de client (această definiție, preluată din cartea „Head First Design Patterns”, mi se pare excelentă). Model de design: Strategie - 4Am specificat deja familia de algoritmi care ne interesează (modalități de a alimenta mașinile) în interfețe separate cu implementări diferite. Le-am despărțit de mașină în sine. Acum, dacă trebuie să facem modificări unui anumit algoritm de realimentare, nu ne va afecta în niciun fel clasele de mașini. Și pentru a obține interschimbabilitatea, trebuie doar să adăugăm o singură metodă setter la clasa noastră de transport :

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;
   }
}
Acum putem schimba strategiile din mers:

public class Main {

   public static void main(String[] args) {

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

       stroller.fill();
   }
}
Dacă cărucioarele pentru copii încep brusc să funcționeze pe benzină, programul nostru va fi gata să facă față acestui scenariu :) Și cam atât! Ați învățat încă un model de design care, fără îndoială, va fi esențial și util atunci când lucrați la proiecte reale :) Până data viitoare!
Comentarii
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION