CIAO! Oggi continueremo a studiare i design pattern e parleremo del factory method pattern. Scoprirai di cosa si tratta e per quali compiti è adatto questo modello. Prenderemo in considerazione questo modello di progettazione nella pratica e ne studieremo la struttura. Per garantire che tutto sia chiaro, è necessario comprendere i seguenti argomenti:
- Ereditarietà in Java.
- Metodi astratti e classi in Java
Quale problema risolve il metodo factory?
Tutti i pattern di factory design hanno due tipi di partecipanti: creatori (le fabbriche stesse) e prodotti (gli oggetti creati dalle fabbriche). Immagina la seguente situazione: abbiamo una fabbrica che produce auto a marchio CodeGym. Sa creare modelli di auto con vari tipi di carrozzeria:- berline
- station wagon
- coupé
- berline CodeGym
- Le station wagon CodeGym
- Coupé CodeGym
- Le berline OneAuto
- Le station wagon OneAuto
- OneAuto coupé
Un po 'sul modello di fabbrica
Lascia che ti ricordi che in precedenza abbiamo creato un piccolo bar virtuale. Con l'aiuto di una semplice fabbrica, abbiamo imparato a creare diversi tipi di caffè. Oggi rielaboreremo questo esempio. Ricordiamo come appariva la nostra caffetteria, con la sua semplice fabbrica. Abbiamo tenuto una lezione di caffè:
public class Coffee {
public void grindCoffee(){
// Grind the coffee
}
public void makeCoffee(){
// Brew the coffee
}
public void pourIntoCup(){
// Pour into a cup
}
}
E diverse classi figlio corrispondenti a specifici tipi di caffè che la nostra fabbrica potrebbe produrre:
public class Americano extends Coffee {}
public class Cappuccino extends Coffee {}
public class CaffeLatte extends Coffee {}
public class Espresso extends Coffee {}
Abbiamo creato un'enumerazione per semplificare l'inserimento degli ordini:
public enum CoffeeType {
ESPRESSO,
AMERICANO,
CAFFE_LATTE,
CAPPUCCINO
}
La stessa fabbrica di caffè aveva questo aspetto:
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;
}
}
E infine, la caffetteria stessa aveva questo aspetto:
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;
}
}
Modernizzazione di una fabbrica semplice
La nostra caffetteria funziona molto bene. Tanto che stiamo pensando di espanderci. Vogliamo aprire alcune nuove sedi. Siamo audaci e intraprendenti, quindi non sforneremo noiosi bar. Vogliamo che ogni negozio abbia un tocco speciale. Per cominciare, quindi, apriremo due sedi: una italiana e una americana. Questi cambiamenti riguarderanno non solo il design degli interni, ma anche le bevande offerte:- nella caffetteria italiana utilizzeremo esclusivamente marche di caffè italiano, con macinatura e tostatura speciali.
- la sede americana avrà porzioni più grandi e serviremo marshmallow con ogni ordine.
public class Americano extends Coffee {}
public class Cappuccino extends Coffee {}
public class CaffeLatte extends Coffee {}
public class Espresso extends Coffee {}
Ma ora ne avremo 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 {}
Dal momento che vogliamo mantenere l'attuale modello di business, vogliamo che il orderCoffee(CoffeeType type)
metodo subisca il minor numero di modifiche possibile. Dai un'occhiata:
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;
}
Quali opzioni abbiamo? Bene, sappiamo già come scrivere una fabbrica, giusto? La cosa più semplice che mi viene subito in mente è scrivere due factory simili e poi passare l'implementazione desiderata al costruttore del nostro coffee shop. In questo modo, la classe del bar non cambierà. Per prima cosa, dobbiamo creare una nuova classe factory, fare in modo che erediti la nostra factory semplice e quindi sovrascrivere il createCoffee(CoffeeType type)
metodo. Scriviamo fabbriche per creare caffè all'italiana e caffè all'americana:
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;
}
}
Ora possiamo passare l'implementazione di fabbrica desiderata a CoffeeShop. Vediamo come sarebbe il codice per ordinare il caffè da diverse caffetterie. Ad esempio, cappuccino all'italiana e all'americana:
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);
}
}
Abbiamo creato due diverse caffetterie, passando a ciascuna la fabbrica desiderata. Da un lato, abbiamo raggiunto il nostro obiettivo, ma dall'altro... In qualche modo questo non va bene per gli imprenditori... Scopriamo cosa c'è che non va. Innanzitutto, l'abbondanza di fabbriche. Che cosa? Ora, per ogni nuova sede, dovremmo creare la propria fabbrica e, in aggiunta a ciò, assicurarci che la relativa fabbrica venga passata al costruttore durante la creazione di una caffetteria? In secondo luogo, è ancora una semplice fabbrica. Appena modernizzato leggermente. Ma siamo qui per imparare un nuovo schema. Terzo, non è possibile un approccio diverso? Sarebbe bello se potessimo inserire tutte le questioni relative alla preparazione del caffè nelCoffeeShop
classe collegando i processi di creazione del caffè e di servizio degli ordini, pur mantenendo una flessibilità sufficiente per realizzare vari stili di caffè. La risposta è sì, possiamo. Questo è chiamato modello di progettazione del metodo di fabbrica.
Da una semplice fabbrica a un metodo di fabbrica
Per risolvere il compito nel modo più efficiente possibile:- Restituiamo il
createCoffee(CoffeeType type)
metodo allaCoffeeShop
classe. - Renderemo questo metodo astratto.
- La
CoffeeShop
classe stessa diventerà astratta. - La
CoffeeShop
classe avrà classi figlio.
CoffeeShop
classe, che attua il createCoffee(CoffeeType type)
metodo secondo le migliori tradizioni dei baristi italiani. Ora, un passo alla volta. Passaggio 1. Rendere la Coffee
classe astratta. Abbiamo due intere famiglie di prodotti diversi. Tuttavia, i caffè italiani e americani hanno un antenato comune: la Coffee
classe. Sarebbe opportuno renderlo astratto:
public abstract class Coffee {
public void makeCoffee(){
// Brew the coffee
}
public void pourIntoCup(){
// Pour into a cup
}
}
Passaggio 2. Rendi CoffeeShop
astratto, con un createCoffee(CoffeeType type)
metodo astratto
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);
}
Passaggio 3. Crea una caffetteria italiana, che è un discendente della caffetteria astratta. Implementiamo il createCoffee(CoffeeType type)
metodo in esso, tenendo conto delle specificità delle ricette italiane.
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;
}
}
Passaggio 4. Facciamo lo stesso per la caffetteria in stile americano
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;
}
}
Passaggio 5. Scopri come appariranno i caffelatte americani e italiani:
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);
}
}
Congratulazioni. Abbiamo appena implementato il modello di progettazione del metodo di fabbrica utilizzando la nostra caffetteria come esempio.
Il principio alla base dei metodi di fabbrica
Ora consideriamo più in dettaglio ciò che abbiamo ottenuto. Il diagramma seguente mostra le classi risultanti. I blocchi verdi sono classi di creatori e i blocchi blu sono classi di prodotti. Quali conclusioni possiamo trarre?- Tutti i prodotti sono implementazioni della
Coffee
classe astratta. - Tutti i creatori sono implementazioni della
CoffeeShop
classe astratta. - Vediamo due gerarchie di classi parallele:
- Gerarchia dei prodotti. Vediamo discendenti italiani e discendenti americani
- Gerarchia dei creatori. Vediamo discendenti italiani e discendenti americani
- La
CoffeeShop
superclasse non ha informazioni su quale prodotto specifico (Coffee
) verrà creato. - La
CoffeeShop
superclasse delega la creazione di un prodotto specifico ai suoi discendenti. - Ogni discendente della
CoffeeShop
classe implementa uncreateCoffee()
metodo factory secondo le proprie caratteristiche specifiche. In altre parole, le implementazioni delle classi producer preparano prodotti specifici basati sulle specifiche della classe producer.
Struttura di un metodo di fabbrica
Il diagramma sopra mostra la struttura generale del pattern del metodo factory. Cos'altro è importante qui?- La classe Creator implementa tutti i metodi che interagiscono con i prodotti, ad eccezione del metodo factory.
- Il metodo astratto
factoryMethod()
deve essere implementato da tutti i discendenti dellaCreator
classe. - La
ConcreteCreator
classe implementa ilfactoryMethod()
metodo, che crea direttamente il prodotto. - Questa classe è responsabile della creazione di prodotti specifici. Questa è l'unica classe con informazioni sulla creazione di questi prodotti.
- Tutti i prodotti devono implementare un'interfaccia comune, ovvero devono essere discendenti di una classe di prodotto comune. Ciò è necessario affinché le classi che utilizzano i prodotti possano operare su di essi come astrazioni, piuttosto che implementazioni specifiche.
GO TO FULL VERSION