Hoi! Vandaag gaan we door met het bestuderen van ontwerppatronen en bespreken we het patroon van de fabrieksmethode.
U zult ontdekken wat het is en voor welke taken dit patroon geschikt is. We zullen dit ontwerppatroon in de praktijk bekijken en de structuur ervan bestuderen. Om ervoor te zorgen dat alles duidelijk is, moet u de volgende onderwerpen begrijpen:
Welke conclusies kunnen we trekken?
Het bovenstaande diagram toont de algemene structuur van het patroon van de fabrieksmethode. Wat is hier verder belangrijk?

- Overerving op Java.
- Abstracte methoden en klassen in Java
Welk probleem lost de fabrieksmethode op?
Alle fabrieksontwerppatronen hebben twee soorten deelnemers: makers (de fabrieken zelf) en producten (de objecten die door de fabrieken zijn gemaakt). Stel je de volgende situatie voor: we hebben een fabriek die auto's met het merk CodeGym produceert. Het weet hoe het modellen van auto's moet maken met verschillende soorten carrosserieën:- sedans
- stationwagons
- coupes
- CodeGym sedans
- CodeGym stationwagons
- CodeGym coupes
- OneAuto sedans
- OneAuto stationwagons
- OneAuto coupés
Een beetje over het fabriekspatroon
Laat me je eraan herinneren dat we eerder een kleine virtuele coffeeshop hebben gebouwd. Met behulp van een eenvoudige fabriek leerden we verschillende soorten koffie te maken. Vandaag gaan we dit voorbeeld herwerken. Laten we ons herinneren hoe onze coffeeshop eruit zag, met zijn eenvoudige fabriek. We hadden een koffieles:
public class Coffee {
public void grindCoffee(){
// Grind the coffee
}
public void makeCoffee(){
// Brew the coffee
}
public void pourIntoCup(){
// Pour into a cup
}
}
En verschillende kinderklassen die overeenkomen met specifieke soorten koffie die onze fabriek zou kunnen produceren:
public class Americano extends Coffee {}
public class Cappuccino extends Coffee {}
public class CaffeLatte extends Coffee {}
public class Espresso extends Coffee {}
We hebben een opsomming gemaakt om het plaatsen van bestellingen gemakkelijk te maken:
public enum CoffeeType {
ESPRESSO,
AMERICANO,
CAFFE_LATTE,
CAPPUCCINO
}
De koffiefabriek zelf zag er zo uit:
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;
}
}
En tot slot zag de coffeeshop zelf er zo uit:
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;
}
}
Een simpele fabriek moderniseren
Onze coffeeshop draait erg goed. Zo erg zelfs dat we overwegen uit te breiden. We willen enkele nieuwe locaties openen. We zijn gedurfd en ondernemend, dus we gaan geen saaie coffeeshops uit de grond stampen. We willen dat elke winkel een speciale twist heeft. Daarom openen we om te beginnen twee locaties: een Italiaanse en een Amerikaanse. Deze wijzigingen hebben niet alleen invloed op het interieurontwerp, maar ook op de aangeboden drankjes:- in de Italiaanse coffeeshop gebruiken we uitsluitend Italiaanse koffiemerken, met speciale maling en branding.
- de Amerikaanse locatie heeft grotere porties en we serveren marshmallows bij elke bestelling.
public class Americano extends Coffee {}
public class Cappuccino extends Coffee {}
public class CaffeLatte extends Coffee {}
public class Espresso extends Coffee {}
Maar nu hebben we er 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 {}
Omdat we het huidige businessmodel willen behouden, willen we dat de orderCoffee(CoffeeType type)
werkwijze zo min mogelijk verandert. Bekijk het eens:
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;
}
Welke opties hebben we? Nou, we weten al hoe we een fabriek moeten schrijven, toch? Het eenvoudigste dat meteen in je opkomt, is om twee vergelijkbare fabrieken te schrijven en vervolgens de gewenste implementatie door te geven aan de constructeur van onze coffeeshop. Hierdoor verandert de klasse van de coffeeshop niet. Eerst moeten we een nieuwe fabrieksklasse maken, deze onze eenvoudige fabriek laten erven en vervolgens de createCoffee(CoffeeType type)
methode overschrijven. Laten we fabrieken schrijven voor het maken van koffie in Italiaanse stijl en koffie in Amerikaanse stijl:
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;
}
}
Nu kunnen we de gewenste fabrieksimplementatie doorgeven aan CoffeeShop. Laten we eens kijken hoe de code voor het bestellen van koffie bij verschillende coffeeshops eruit zou zien. Bijvoorbeeld cappuccino in Italiaanse en Amerikaanse stijl:
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);
}
}
We creëerden twee verschillende coffeeshops en gaven aan elk de gewenste fabriek door. Aan de ene kant hebben we ons doel bereikt, maar aan de andere kant... Op de een of andere manier zit dit niet goed bij de ondernemers... Laten we uitzoeken wat er mis is. Ten eerste de overvloed aan fabrieken. Wat? Nu moeten we voor elke nieuwe locatie een eigen fabriek maken en er daarnaast voor zorgen dat de betreffende fabriek wordt doorgegeven aan de constructeur bij het maken van een coffeeshop? Ten tweede is het nog steeds een eenvoudige fabriek. Net iets gemoderniseerd. Maar we zijn hier om een nieuw patroon te leren. Ten derde, is een andere benadering niet mogelijk? Het zou mooi zijn als we alle zaken rond de koffiebereiding in deCoffeeShop
klasse door de processen van koffie maken en bedienen van bestellingen te koppelen en tegelijkertijd voldoende flexibiliteit te behouden om verschillende soorten koffie te zetten. Het antwoord is ja, dat kunnen we. Dit wordt het ontwerppatroon van de fabrieksmethode genoemd.
Van een simpele fabriek naar een fabrieksmethode
Om de taak zo efficiënt mogelijk op te lossen:- We geven de
createCoffee(CoffeeType type)
methode terug aan deCoffeeShop
klas. - We zullen deze methode abstract maken.
- De
CoffeeShop
klas zelf wordt abstract. - De
CoffeeShop
klas krijgt kinderlessen.
CoffeeShop
uitvoert . createCoffee(CoffeeType type)
Nu stap voor stap. Stap 1. Maak de Coffee
klas abstract. We hebben twee hele families met verschillende producten. Toch hebben de Italiaanse en Amerikaanse koffiesoorten een gemeenschappelijke voorouder: de Coffee
klasse. Het zou juist zijn om het abstract te maken:
public abstract class Coffee {
public void makeCoffee(){
// Brew the coffee
}
public void pourIntoCup(){
// Pour into a cup
}
}
Stap 2. Maak CoffeeShop
abstract, met een abstracte createCoffee(CoffeeType type)
methode
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);
}
Stap 3. Creëer een Italiaanse coffeeshop, die een afstammeling is van de abstracte coffeeshop. We implementeren de createCoffee(CoffeeType type)
methode erin, rekening houdend met de specifieke kenmerken van Italiaanse recepten.
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;
}
}
Stap 4. Hetzelfde doen we voor de Amerikaanse coffeeshop
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;
}
}
Stap 5. Bekijk hoe Amerikaanse en Italiaanse lattes eruit zullen zien:
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);
}
}
Gefeliciteerd. We hebben zojuist het ontwerppatroon van de fabrieksmethode geïmplementeerd met onze coffeeshop als voorbeeld.
Het principe achter fabrieksmethodes
Laten we nu in meer detail bekijken wat we hebben. Het onderstaande diagram toont de resulterende klassen. De groene blokken zijn makerklassen en de blauwe blokken zijn productklassen.
- Alle producten zijn implementaties van de abstracte
Coffee
klasse. - Alle makers zijn implementaties van de abstracte
CoffeeShop
klasse. - We zien twee parallelle klassenhiërarchieën:
- Hiërarchie van producten. We zien Italiaanse nakomelingen en Amerikaanse nakomelingen
- Hiërarchie van makers. We zien Italiaanse nakomelingen en Amerikaanse nakomelingen
- De
CoffeeShop
superklasse heeft geen informatie over welk specifiek product (Coffee
) zal worden gemaakt. - De
CoffeeShop
superklasse delegeert de creatie van een specifiek product aan zijn nakomelingen. - Elke afstammeling van de
CoffeeShop
klasse implementeert eencreateCoffee()
fabrieksmethode in overeenstemming met zijn eigen specifieke kenmerken. Met andere woorden, de implementaties van de producentenklassen bereiden specifieke producten voor op basis van de specifieke kenmerken van de producentenklasse.
Structuur van een fabrieksmethode

- De klasse Creator implementeert alle methoden die interactie hebben met producten, behalve de fabrieksmethode.
- De abstracte
factoryMethod()
methode moet worden geïmplementeerd door alle afstammelingen van deCreator
klasse. - De
ConcreteCreator
klasse implementeert defactoryMethod()
methode, die direct het product creëert. - Deze klasse is verantwoordelijk voor het maken van specifieke producten. Dit is de enige les met informatie over het maken van deze producten.
- Alle producten moeten een gemeenschappelijke interface implementeren, dwz ze moeten afstammelingen zijn van een gemeenschappelijke productklasse. Dit is nodig zodat klassen die producten gebruiken erop kunnen werken als abstracties in plaats van als specifieke implementaties.
GO TO FULL VERSION