Salut! Aujourd'hui, nous allons continuer à étudier les modèles de conception et nous discuterons du modèle de méthode d'usine. Vous découvrirez de quoi il s'agit et à quelles tâches ce modèle convient. Nous allons considérer ce modèle de conception dans la pratique et étudier sa structure. Pour vous assurer que tout est clair, vous devez comprendre les sujets suivants :
- Héritage en Java.
- Méthodes abstraites et classes en Java
Quel problème la méthode de l'usine résout-elle ?
Tous les modèles de conception d'usine ont deux types de participants : les créateurs (les usines elles-mêmes) et les produits (les objets créés par les usines). Imaginez la situation suivante : nous avons une usine qui produit des voitures de marque CodeGym. Il sait créer des modèles de voitures avec différents types de carrosseries :- berlines
- breaks
- coupés
- Berlines CodeGym
- Les breaks CodeGym
- Coupés CodeGym
- Berlines OneAuto
- Les breaks OneAuto
- Coupés OneAuto
Un peu sur le modèle d'usine
Permettez-moi de vous rappeler que nous avons précédemment construit un petit café virtuel. Avec l'aide d'une usine simple, nous avons appris à créer différents types de café. Aujourd'hui, nous allons retravailler cet exemple. Rappelons-nous à quoi ressemblait notre café, avec sa simple usine. Nous avons eu un cours de café:
public class Coffee {
public void grindCoffee(){
// Grind the coffee
}
public void makeCoffee(){
// Brew the coffee
}
public void pourIntoCup(){
// Pour into a cup
}
}
Et plusieurs classes enfants correspondant à des types de café spécifiques que notre usine pourrait produire :
public class Americano extends Coffee {}
public class Cappuccino extends Coffee {}
public class CaffeLatte extends Coffee {}
public class Espresso extends Coffee {}
Nous avons créé une énumération pour faciliter la passation de commandes :
public enum CoffeeType {
ESPRESSO,
AMERICANO,
CAFFE_LATTE,
CAPPUCCINO
}
L'usine de café elle-même ressemblait à ceci :
public class SimpleCoffeeFactory {
public Coffee createCoffee(CoffeeType type) {
Coffee coffee = null;
switch (type) {
case AMERICANO:
coffee = new Americano();
break;
case ESPRESSO:
coffee = new Espresso();
break;
case CAPPUCCINO:
coffee = new Cappuccino();
break;
case CAFFE_LATTE:
coffee = new CaffeLatte();
break;
}
return coffee;
}
}
Et enfin, le café lui-même ressemblait à ceci :
public class CoffeeShop {
private final SimpleCoffeeFactory coffeeFactory;
public CoffeeShop(SimpleCoffeeFactory coffeeFactory) {
this.coffeeFactory = coffeeFactory;
}
public Coffee orderCoffee(CoffeeType type) {
Coffee coffee = coffeeFactory.createCoffee(type);
coffee.grindCoffee();
coffee.makeCoffee();
coffee.pourIntoCup();
System.out.println("Here's your coffee! Thanks! Come again!");
return coffee;
}
}
Moderniser une usine simple
Notre café fonctionne très bien. A tel point que nous envisageons de nous agrandir. Nous souhaitons ouvrir de nouveaux sites. Nous sommes audacieux et entreprenants, nous ne créerons donc pas de cafés ennuyeux. Nous voulons que chaque magasin ait une touche spéciale. Ainsi, dans un premier temps, nous ouvrirons deux sites : un italien et un américain. Ces changements affecteront non seulement le design intérieur, mais également les boissons proposées :- dans le café italien, nous utiliserons exclusivement des marques de café italiennes, avec une mouture et une torréfaction spéciales.
- l'emplacement américain aura de plus grandes portions, et nous servirons des guimauves avec chaque commande.
public class Americano extends Coffee {}
public class Cappuccino extends Coffee {}
public class CaffeLatte extends Coffee {}
public class Espresso extends Coffee {}
Mais maintenant nous en aurons 8 :
public class ItalianStyleAmericano extends Coffee {}
public class ItalianStyleCappucino extends Coffee {}
public class ItalianStyleCaffeLatte extends Coffee {}
public class ItalianStyleEspresso extends Coffee {}
public class AmericanStyleAmericano extends Coffee {}
public class AmericanStyleCappucino extends Coffee {}
public class AmericanStyleCaffeLatte extends Coffee {}
public class AmericanStyleEspresso extends Coffee {}
Puisque nous voulons conserver le modèle économique actuel, nous voulons que la orderCoffee(CoffeeType type)
méthode subisse le moins de changements possible. Jetez un coup d'oeil:
public Coffee orderCoffee(CoffeeType type) {
Coffee coffee = coffeeFactory.createCoffee(type);
coffee.grindCoffee();
coffee.makeCoffee();
coffee.pourIntoCup();
System.out.println("Here's your coffee! Thanks! Come again!");
return coffee;
}
Quelles options avons-nous? Eh bien, nous savons déjà comment écrire une usine, non ? La chose la plus simple qui nous vient immédiatement à l'esprit est d'écrire deux usines similaires, puis de transmettre l'implémentation souhaitée au constructeur de notre café. En faisant cela, la classe du café ne changera pas. Tout d'abord, nous devons créer une nouvelle classe de fabrique, lui faire hériter de notre fabrique simple, puis remplacer la createCoffee(CoffeeType type)
méthode. Écrivons des usines pour créer du café à l'italienne et du café à l'américaine :
public class SimpleItalianCoffeeFactory extends SimpleCoffeeFactory {
@Override
public Coffee createCoffee(CoffeeType type) {
Coffee coffee = null;
switch (type) {
case AMERICANO:
coffee = new ItalianStyleAmericano();
break;
case ESPRESSO:
coffee = new ItalianStyleEspresso();
break;
case CAPPUCCINO:
coffee = new ItalianStyleCappuccino();
break;
case CAFFE_LATTE:
coffee = new ItalianStyleCaffeLatte();
break;
}
return coffee;
}
}
public class SimpleAmericanCoffeeFactory extends SimpleCoffeeFactory{
@Override
public Coffee createCoffee (CoffeeType type) {
Coffee coffee = null;
switch (type) {
case AMERICANO:
coffee = new AmericanStyleAmericano();
break;
case ESPRESSO:
coffee = new AmericanStyleEspresso();
break;
case CAPPUCCINO:
coffee = new AmericanStyleCappuccino();
break;
case CAFFE_LATTE:
coffee = new AmericanStyleCaffeLatte();
break;
}
return coffee;
}
}
Nous pouvons maintenant transmettre l'implémentation d'usine souhaitée à CoffeeShop. Voyons à quoi ressemblerait le code pour commander du café dans différents cafés. Par exemple, cappuccino à l'italienne et à l'américaine :
public class Main {
public static void main(String[] args) {
/*
Order an Italian-style cappuccino:
1. Create a factory for making Italian coffee
2. Create a new coffee shop, passing the Italian coffee factory to it through the constructor
3. Order our coffee
*/
SimpleItalianCoffeeFactory italianCoffeeFactory = new SimpleItalianCoffeeFactory();
CoffeeShop italianCoffeeShop = new CoffeeShop(italianCoffeeFactory);
italianCoffeeShop.orderCoffee(CoffeeType.CAPPUCCINO);
/*
Order an American-style cappuccino
1. Create a factory for making American coffee
2. Create a new coffee shop, passing the American coffee factory to it through the constructor
3. Order our coffee
*/
SimpleAmericanCoffeeFactory americanCoffeeFactory = new SimpleAmericanCoffeeFactory();
CoffeeShop americanCoffeeShop = new CoffeeShop(americanCoffeeFactory);
americanCoffeeShop.orderCoffee(CoffeeType.CAPPUCCINO);
}
}
Nous avons créé deux cafés différents, en passant l'usine souhaitée à chacun. D'un côté, nous avons atteint notre objectif, mais de l'autre... D'une certaine manière, cela ne plaît pas aux entrepreneurs... Voyons ce qui ne va pas. Premièrement, l'abondance des usines. Quoi? Maintenant, pour chaque nouvel emplacement, nous sommes censés créer sa propre usine et, en plus, nous assurer que l'usine concernée est transmise au constructeur lors de la création d'un café ? Deuxièmement, c'est encore une simple usine. Juste modernisé légèrement. Mais nous sommes ici pour apprendre un nouveau modèle. Troisièmement, une approche différente n'est-elle pas possible ? Ce serait formidable si nous pouvions mettre tous les problèmes liés à la préparation du café dans leCoffeeShop
classe en reliant les processus de création de café et de service des commandes, tout en conservant simultanément une flexibilité suffisante pour préparer différents styles de café. La réponse est oui, nous le pouvons. C'est ce qu'on appelle le modèle de conception de méthode d'usine.
D'une simple usine à une méthode d'usine
Pour résoudre la tâche le plus efficacement possible :- Nous renvoyons la
createCoffee(CoffeeType type)
méthode à laCoffeeShop
classe. - Nous rendrons cette méthode abstraite.
- La
CoffeeShop
classe elle-même deviendra abstraite. - La
CoffeeShop
classe aura des classes enfants.
CoffeeShop
classe, qui met en œuvre la createCoffee(CoffeeType type)
méthode dans le respect des meilleures traditions des baristas italiens. Maintenant, une étape à la fois. Étape 1. Rendre la Coffee
classe abstraite. Nous avons deux familles entières de produits différents. Pourtant, les cafés italiens et américains ont un ancêtre commun : la Coffee
classe. Il conviendrait de le rendre abstrait :
public abstract class Coffee {
public void makeCoffee(){
// Brew the coffee
}
public void pourIntoCup(){
// Pour into a cup
}
}
Étape 2. Rendre CoffeeShop
abstrait, avec une createCoffee(CoffeeType type)
méthode abstraite
public abstract class CoffeeShop {
public Coffee orderCoffee(CoffeeType type) {
Coffee coffee = createCoffee(type);
coffee.makeCoffee();
coffee.pourIntoCup();
System.out.println("Here's your coffee! Thanks! Come again!");
return coffee;
}
protected abstract Coffee createCoffee(CoffeeType type);
}
Étape 3. Créez un café italien, qui est un descendant du café abstrait. Nous y implémentons la createCoffee(CoffeeType type)
méthode en tenant compte des spécificités des recettes italiennes.
public class ItalianCoffeeShop extends CoffeeShop {
@Override
public Coffee createCoffee (CoffeeType type) {
Coffee coffee = null;
switch (type) {
case AMERICANO:
coffee = new ItalianStyleAmericano();
break;
case ESPRESSO:
coffee = new ItalianStyleEspresso();
break;
case CAPPUCCINO:
coffee = new ItalianStyleCappuccino();
break;
case CAFFE_LATTE:
coffee = new ItalianStyleCaffeLatte();
break;
}
return coffee;
}
}
Étape 4. Nous faisons la même chose pour le café à l'américaine
public class AmericanCoffeeShop extends CoffeeShop {
@Override
public Coffee createCoffee(CoffeeType type) {
Coffee coffee = null;
switch (type) {
case AMERICANO:
coffee = new AmericanStyleAmericano();
break;
case ESPRESSO:
coffee = new AmericanStyleEspresso();
break;
case CAPPUCCINO:
coffee = new AmericanStyleCappuccino();
break;
case CAFFE_LATTE:
coffee = new AmericanStyleCaffeLatte();
break;
}
return coffee;
}
}
Étape 5. Découvrez à quoi ressembleront les lattes américains et italiens :
public class Main {
public static void main(String[] args) {
CoffeeShop italianCoffeeShop = new ItalianCoffeeShop();
italianCoffeeShop.orderCoffee(CoffeeType.CAFFE_LATTE);
CoffeeShop americanCoffeeShop = new AmericanCoffeeShop();
americanCoffeeShop.orderCoffee(CoffeeType.CAFFE_LATTE);
}
}
Toutes nos félicitations. Nous venons d'implémenter le modèle de conception de la méthode d'usine en utilisant notre café comme exemple.
Le principe des méthodes d'usine
Examinons maintenant plus en détail ce que nous avons obtenu. Le schéma ci-dessous montre les classes résultantes. Les blocs verts sont des classes de créateurs et les blocs bleus sont des classes de produits. Quelles conclusions pouvons-nous tirer ?- Tous les produits sont des implémentations de la
Coffee
classe abstraite. - Tous les créateurs sont des implémentations de la
CoffeeShop
classe abstraite. - Nous voyons deux hiérarchies de classes parallèles :
- Hiérarchie des produits. On voit des descendants italiens et des descendants américains
- Hiérarchie des créateurs. On voit des descendants italiens et des descendants américains
- La
CoffeeShop
superclasse n'a aucune information sur le produit spécifique (Coffee
) qui sera créé. - La
CoffeeShop
superclasse délègue la création d'un produit spécifique à ses descendants. - Chaque descendant de la
CoffeeShop
classe implémente unecreateCoffee()
méthode de fabrique selon ses propres spécificités. En d'autres termes, les implémentations des classes de producteurs préparent des produits spécifiques basés sur les spécificités de la classe de producteurs.
Structure d'une méthode d'usine
Le diagramme ci-dessus montre la structure générale du modèle de méthode d'usine. Quoi d'autre est important ici?- La classe Creator implémente toutes les méthodes qui interagissent avec les produits, à l'exception de la méthode factory.
- La méthode abstraite
factoryMethod()
doit être implémentée par tous les descendants de laCreator
classe. - La
ConcreteCreator
classe implémente lafactoryMethod()
méthode, qui crée directement le produit. - Cette classe est responsable de la création de produits spécifiques. C'est la seule classe avec des informations sur la création de ces produits.
- Tous les produits doivent implémenter une interface commune, c'est-à-dire qu'ils doivent être des descendants d'une classe de produits commune. Cela est nécessaire pour que les classes qui utilisent des produits puissent fonctionner dessus comme des abstractions, plutôt que comme des implémentations spécifiques.
GO TO FULL VERSION