JavaRush /Java Blog /Random-IT /Modello di progettazione “Strategia”

Modello di progettazione “Strategia”

Pubblicato nel gruppo Random-IT
Ciao! Nelle lezioni precedenti abbiamo già incontrato il concetto di “design pattern”. Nel caso te ne fossi dimenticato, te lo ricordiamo: questo termine denota una certa soluzione standard a un problema comune nella programmazione. Modello di progettazione “Strategia” - 1In JavaRush diciamo spesso che la risposta a quasi tutte le domande può essere cercata su Google. Pertanto, probabilmente qualcuno ha già risolto con successo un problema simile al tuo. Pertanto, i modelli sono soluzioni testate nel tempo e testate nella pratica ai problemi o metodi più comuni per risolvere situazioni problematiche. Queste sono proprio le “biciclette” che in nessun caso devi inventare da solo, ma devi sapere come e quando applicarle :) Un altro compito dei modelli è portare l'architettura a un unico standard. Leggere il codice di qualcun altro non è un compito facile! Ognuno lo scrive in modo diverso, perché lo stesso problema può essere risolto in tanti modi. Ma l'uso dei pattern consente a diversi programmatori di comprendere la logica del programma senza approfondire ogni riga di codice (anche se la vedono per la prima volta!) Oggi esamineremo uno dei pattern più comuni chiamato “Strategia”. Modello di progettazione “Strategia” - 2Immaginiamo di scrivere un programma che funzioni attivamente con l'oggetto Car. In questo caso non è nemmeno particolarmente importante cosa fa esattamente il nostro programma. Per fare ciò, abbiamo creato un sistema di ereditarietà con una classe genitore Autoe tre classi figlie: Sedan, Trucke F1Car.
public class Auto {

   public void gas() {
       System.out.println("Едем вперед");
   }

   public void stop() {

       System.out.println("Тормозим!");
   }
}

public class Sedan extends Auto {
}

public class Truck extends Auto {
}

public class F1Car extends Auto {
}
Tutte e tre le classi figlie ereditano due metodi standard dalla madre - gas()e stop() il nostro programma è assolutamente semplice: le auto possono solo avanzare e frenare. Continuando il nostro lavoro, abbiamo deciso di aggiungere un nuovo metodo alle auto: fill()(rifornimento). Aggiungiamolo alla classe genitore Auto:
public class Auto {

   public void gas() {
       System.out.println("Едем вперед");
   }

   public void stop() {

       System.out.println("Тормозим!");
   }

   public void fill() {
       System.out.println("Заправить бензин!");
   }
}
Sembrerebbe che possano sorgere problemi in una situazione così semplice? Ebbene, in effetti, sono già sorti... Modello di progettazione “Strategia” - 3
public class ChildrenBuggies extends Auto {

   public void fill() {

       //хм... Это детский багги, его не надо заправлять :/
   }
}
Nel nostro programma è apparsa un'auto che non rientra nel concetto generale: un passeggino per bambini. Può essere a pedali o radiocomandato, ma una cosa è certa: non c'è nessun posto dove metterci la benzina. Il nostro schema di ereditarietà ci ha portato a distribuire metodi comuni anche alle classi che non ne hanno bisogno. Cosa dovremmo fare in una situazione del genere? Bene, ad esempio, puoi sovrascrivere il metodo fill()nella classe ChildrenBuggiesin modo che quando provi a fare rifornimento al buggy, non accada nulla:
public class ChildrenBuggies extends Auto {

   @Override
   public void fill() {
       System.out.println("Игрушечную машину нельзя заправить!");
   }
}
Ma questa soluzione difficilmente può essere definita vincente, almeno a causa della duplicazione del codice. Ad esempio, la maggior parte delle classi utilizzerà un metodo della classe genitore, ma altre classi saranno costrette a sovrascriverlo. Se abbiamo 15 classi e in 5-6 siamo costretti a sovrascrivere il comportamento, la duplicazione del codice diventerà piuttosto estesa. Forse le interfacce possono aiutarci? Ad esempio, questo:
public interface Fillable {

   public void fill();
}
Creeremo un'interfaccia Fillablecon un metodo fill(). Di conseguenza, le auto che necessitano di rifornimento implementeranno questa interfaccia, ma le altre auto (ad esempio il nostro buggy) no. Ma anche questa opzione non è adatta a noi. La nostra gerarchia di classi potrebbe crescere fino a raggiungere un numero molto elevato in futuro (immagina quanti diversi tipi di auto ci sono nel mondo). Abbiamo abbandonato la precedente opzione di ereditarietà perché non volevamo sovrascrivere l'opzione fill(). Qui dovremo implementarlo in ogni classe! E se ne avessimo 50? E se vengono apportate modifiche frequenti al nostro programma (e nei programmi reali ciò accadrà quasi sempre!), Dovremo correre con la lingua fuori tra tutte le 50 classi e modificare manualmente il comportamento di ciascuna di esse. Quindi cosa dovremmo fare alla fine? Per risolvere il nostro problema, scegliamo un percorso diverso. Vale a dire, separiamo il comportamento della nostra classe dalla classe stessa. Cosa significa? Come sai, qualsiasi oggetto ha uno stato (un insieme di dati) e un comportamento (un insieme di metodi). Il comportamento della nostra classe macchina è costituito da tre metodi: gas(), stop()e fill(). I primi due metodi vanno bene. Ma sposteremo il terzo metodo fuori dalla classe Auto. Questa sarà la separazione del comportamento dalla classe (più precisamente, separiamo solo una parte del comportamento: i primi due metodi rimangono in vigore). Dove dovremmo spostare il nostro metodo fill()? Non mi viene in mente niente subito :/ Sembrava essere completamente al suo posto. Lo sposteremo in un'interfaccia separata - FillStrategy!
public interface FillStrategy {

   public void fill();
}
Perché abbiamo bisogno di questa interfaccia? È semplice. Ora possiamo creare diverse classi che implementeranno questa interfaccia:
public class HybridFillStrategy implements FillStrategy {

   @Override
   public void fill() {
       System.out.println("Заправляем бензином or электричеством на выбор!");
   }
}

public class F1PitstopStrategy implements FillStrategy {

   @Override
   public void fill() {
       System.out.println("Заправляем бензин только после всех остальных procedures пит-стопа!");
   }
}

public class StandartFillStrategy implements FillStrategy {
   @Override
   public void fill() {
       System.out.println("Просто заправляем бензин!");
   }
}
Abbiamo creato tre strategie di comportamento: per le auto convenzionali, per le ibride e per le auto di Formula 1. Ciascuna strategia implementa un algoritmo di rifornimento separato. Nel nostro caso, questo viene semplicemente inviato alla console, ma potrebbe esserci una logica complessa all'interno del metodo. Cosa dovremmo fare adesso?
public class Auto {

   FillStrategy fillStrategy;

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

   public void gas() {
       System.out.println("Едем вперед");
   }

   public void stop() {
       System.out.println("Тормозим!");
   }

}
Usiamo la nostra interfaccia FillStrategycome campo nella classe genitore Auto. Nota: non specifichiamo un'implementazione specifica, ma utilizziamo piuttosto l'interfaccia. E avremo bisogno di implementazioni specifiche dell'interfaccia FillStrategynelle classi delle auto per bambini:
public class F1Car extends Auto {

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

public class HybridAuto extends Auto {

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

public class Sedan extends Auto {

   public Sedan() {
       this.fillStrategy = new StandartFillStrategy();
   }
}
Vediamo cosa abbiamo ottenuto:
public class Main {

   public static void main(String[] args) {

       Auto sedan = new Sedan();
       Auto hybrid = new HybridAuto();
       Auto f1car = new F1Car();

       sedan.fill();
       hybrid.fill();
       f1car.fill();
   }
}
Uscita console:

Просто заправляем бензин!
Заправляем бензином or электричеством на выбор!
Заправляем бензин только после всех остальных procedures пит-стопа!
Ottimo, il processo di rifornimento funziona come dovrebbe! A proposito, nulla ci impedisce di utilizzare la strategia come parametro nel costruttore! Ad esempio, in questo modo:
public class Auto {

   private FillStrategy fillStrategy;

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

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

   public void gas() {
       System.out.println("Едем вперед");
   }

   public void stop() {
       System.out.println("Тормозим!");
   }
}

public class Sedan extends Auto {

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



public class HybridAuto extends Auto {

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

public class F1Car extends Auto {

   public F1Car() {
       super(new F1PitstopStrategy());
   }
}
Eseguiamo il nostro metodo main()(rimane invariato) e otteniamo lo stesso risultato! Uscita console:

Просто заправляем бензин!
Заправляем бензином or электричеством на выбор!
Заправляем бензин только после всех остальных procedures пит-стопа!
Il modello Strategy definisce una famiglia di algoritmi, incapsula ciascuno di essi e garantisce che siano intercambiabili. Permette di modificare gli algoritmi indipendentemente dal loro utilizzo lato client (questa definizione è tratta dal libro “Exploring Design Patterns” e mi sembra estremamente riuscita). Modello di progettazione “Strategia” - 4Abbiamo isolato la famiglia di algoritmi che ci interessano (tipi di auto da rifornimento) in interfacce separate con diverse implementazioni. Li abbiamo separati dall'essenza stessa dell'auto. Pertanto, ora, se dobbiamo apportare modifiche a questo o quel processo di rifornimento, ciò non influirà in alcun modo sulle nostre classi di auto. Per quanto riguarda l'intercambiabilità, per ottenerla dobbiamo solo aggiungere un metodo setter alla nostra classe Auto:
public class Auto {

   FillStrategy fillStrategy;

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

   public void gas() {
       System.out.println("Едем вперед");
   }

   public void stop() {
       System.out.println("Тормозим!");
   }

   public void setFillStrategy(FillStrategy fillStrategy) {
       this.fillStrategy = fillStrategy;
   }
}
Ora possiamo cambiare strategia al volo:
public class Main {

   public static void main(String[] args) {

       ChildrenBuggies buggies = new ChildrenBuggies();
       buggies.setFillStrategy(new StandartFillStrategy());

       buggies.fill();
   }
}
Se all'improvviso i buggy dei bambini iniziano a riempirsi di benzina, il nostro programma sarà pronto per uno scenario del genere :) Questo è tutto, in realtà! Hai imparato un altro modello di progettazione, di cui avrai sicuramente bisogno e che ti aiuterà più di una volta quando lavorerai su progetti reali :) Ci vediamo di nuovo!
Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION