JavaRush /Blog Java /Random-FR /Modèle de conception « Stratégie »

Modèle de conception « Stratégie »

Publié dans le groupe Random-FR
Bonjour! Dans des conférences précédentes, nous avons déjà rencontré un concept tel que « modèle de conception ». Au cas où vous l'auriez oublié, rappelons-le : ce terme désigne une certaine solution standard à un problème courant en programmation. Modèle de conception « Stratégie » - 1Chez JavaRush, nous disons souvent que la réponse à presque toutes les questions peut être recherchée sur Google. Par conséquent, quelqu’un a probablement déjà résolu avec succès un problème similaire au vôtre. Ainsi, les modèles sont des solutions éprouvées et éprouvées par la pratique aux problèmes ou aux méthodes les plus courantes pour résoudre des situations problématiques. Ce sont ces mêmes « vélos » qu'il n'est en aucun cas nécessaire d'inventer soi-même, mais il faut savoir comment et quand les appliquer :) Une autre tâche des modèles est d'amener l'architecture à un standard unique. Lire le code de quelqu’un d’autre n’est pas une tâche facile ! Tout le monde l’écrit différemment, car le même problème peut être résolu de plusieurs manières. Mais l'utilisation de modèles permet à différents programmeurs de comprendre la logique du programme sans approfondir chaque ligne de code (même s'ils la voient pour la première fois !). Aujourd'hui, nous allons examiner l'un des modèles les plus courants appelé « Stratégie ». Patron de conception « Stratégie » - 2Imaginons que nous écrivions un programme qui fonctionne activement avec l'objet Car. Dans ce cas, ce que fait exactement notre programme n’est même pas particulièrement important. Pour ce faire, nous avons créé un système d'héritage avec une classe parent Autoet trois classes enfants : Sedan, Trucket 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 {
}
Les trois classes d'enfants héritent de deux méthodes standard des parents - gas()et stop() notre programme est tout à fait simple : les voitures ne peuvent qu'avancer et freiner. Poursuivant notre travail, nous avons décidé d'ajouter une nouvelle méthode aux voitures - fill()(faire le plein). Ajoutons-le à la classe parentAuto :
public class Auto {

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

   public void stop() {

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

   public void fill() {
       System.out.println("Заправить бензин!");
   }
}
Il semblerait que des problèmes puissent survenir dans une situation aussi simple ? Eh bien, en fait, ils sont déjà apparus... Modèle de conception « Stratégie » - 3
public class ChildrenBuggies extends Auto {

   public void fill() {

       //хм... Это детский багги, его не надо заправлять :/
   }
}
Une voiture est apparue dans notre programme qui ne rentre pas dans le concept général : une poussette pour enfants. Il peut être alimenté par pédale ou radiocommandé, mais une chose est sûre : il n'y a nulle part où y mettre de l'essence. Notre système d'héritage nous a amené à donner des méthodes communes même aux classes qui n'en ont pas besoin. Que devons-nous faire dans une telle situation ? Eh bien, par exemple, vous pouvez remplacer la méthode fill()dans la classe ChildrenBuggiesafin que lorsque vous essayez de faire le plein du buggy, rien ne se passe :
public class ChildrenBuggies extends Auto {

   @Override
   public void fill() {
       System.out.println("Игрушечную машину нельзя заправить!");
   }
}
Mais cette solution peut difficilement être qualifiée de réussie, du moins en raison de la duplication de code. Par exemple, la plupart des classes utiliseront une méthode de la classe parent, mais les autres classes seront obligées de la remplacer. Si nous avons 15 classes et qu'en 5-6 nous sommes obligés de remplacer le comportement, la duplication de code deviendra assez étendue. Peut-être que les interfaces peuvent nous aider ? Par exemple, celui-ci :
public interface Fillable {

   public void fill();
}
Nous allons créer une interface Fillableavec une seule méthode fill(). En conséquence, les voitures qui doivent être ravitaillées mettront en œuvre cette interface, mais pas les autres voitures (par exemple, notre buggy). Mais cette option ne nous conviendra pas non plus. Notre hiérarchie de classes pourrait atteindre un très grand nombre à l’avenir (imaginez combien de types de voitures différents il existe dans le monde). Nous avons abandonné l'option d'héritage précédente car nous ne voulions pas remplacer le fill(). Ici, nous devrons l’implémenter dans chaque classe ! Et si nous en avions 50 ? Et si des changements fréquents sont apportés à notre programme (et dans les programmes réels, ce sera presque toujours le cas !), nous devrons courir la langue pendante entre les 50 classes et modifier manuellement le comportement de chacune d'elles. Alors que devrions-nous faire au final ? Pour résoudre notre problème, choisissons une voie différente. Autrement dit, séparons le comportement de notre classe de la classe elle-même. Qu'est-ce que ça veut dire? Comme vous le savez, tout objet possède un état (un ensemble de données) et un comportement (un ensemble de méthodes). Le comportement de notre classe de machines se compose de trois méthodes - gas(), stop()et fill(). Les deux premières méthodes conviennent. Mais nous allons déplacer la troisième méthode en dehors de la classe Auto. Ce sera la séparation du comportement de la classe (plus précisément, on ne sépare qu'une partie du comportement - les deux premières méthodes restent en place). Où devons-nous déplacer notre méthode fill()? Rien ne me vient immédiatement à l'esprit :/ Il semblait complètement à sa place. Nous allons le déplacer vers une interface distincte -FillStrategy !
public interface FillStrategy {

   public void fill();
}
Pourquoi avons-nous besoin de cette interface ? C'est simple. Nous pouvons maintenant créer plusieurs classes qui implémenteront cette interface :
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("Просто заправляем бензин!");
   }
}
Nous avons créé trois stratégies comportementales : pour les voitures conventionnelles, pour les hybrides et pour les voitures de Formule 1. Chaque stratégie implémente un algorithme de ravitaillement distinct. Dans notre cas, il s'agit simplement d'une sortie vers la console, mais il peut y avoir une logique complexe à l'intérieur de la méthode. Que devrions-nous faire avec cela ensuite ?
public class Auto {

   FillStrategy fillStrategy;

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

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

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

}
Nous utilisons notre interface FillStrategycomme champ dans la classe parent Auto. Attention : nous ne spécifions pas d'implémentation spécifique, mais utilisons plutôt l'interface. Et nous aurons besoin d’implémentations spécifiques de l’interface FillStrategydans les classes de voitures enfants :
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();
   }
}
Voyons ce que nous avons obtenu :
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();
   }
}
Sortie de la console :

Просто заправляем бензин!
Заправляем бензином or электричеством на выбор!
Заправляем бензин только после всех остальных procedures пит-стопа!
Génial, le processus de ravitaillement fonctionne comme il se doit ! D’ailleurs, rien ne nous empêche d’utiliser la stratégie comme paramètre dans le constructeur ! Par exemple, comme ceci :
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());
   }
}
Exécutons notre méthode main()(elle reste inchangée) et obtenons le même résultat ! Sortie de la console :

Просто заправляем бензин!
Заправляем бензином or электричеством на выбор!
Заправляем бензин только после всех остальных procedures пит-стопа!
Le modèle Strategy définit une famille d’algorithmes, encapsule chacun d’eux et garantit qu’ils sont interchangeables. Il permet de modifier les algorithmes quelle que soit leur utilisation côté client (cette définition est tirée du livre « Exploring Design Patterns » et me semble extrêmement réussie). Modèle de conception « Stratégie » - 4Nous avons isolé la famille d'algorithmes qui nous intéresse (types de voitures ravitailleurs) dans des interfaces distinctes avec plusieurs implémentations. Nous les avons séparés de l'essence même de la voiture. Par conséquent, si nous devons désormais apporter des modifications à tel ou tel processus de ravitaillement en carburant, cela n'affectera en rien nos classes de voitures. Quant à l'interchangeabilité, pour y parvenir il nous suffit d'ajouter une méthode setter à notre 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;
   }
}
Nous pouvons désormais changer de stratégie à la volée :
public class Main {

   public static void main(String[] args) {

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

       buggies.fill();
   }
}
Si soudainement les poussettes des enfants commencent à être remplies d'essence, notre programme sera prêt pour un tel scénario :) C'est tout, en fait ! Vous avez appris un autre design pattern, dont vous aurez sans aucun doute besoin et qui vous aidera plus d'une fois lorsque vous travaillerez sur de vrais projets :) A bientôt !
Commentaires
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION