Hallo! Heute werden wir uns weiterhin mit Entwurfsmustern befassen und das Fabrikmethodenmuster diskutieren. Sie erfahren, was es ist und für welche Aufgaben dieses Muster geeignet ist. Wir werden dieses Entwurfsmuster in der Praxis betrachten und seine Struktur untersuchen. Um sicherzustellen, dass alles klar ist, müssen Sie die folgenden Themen verstehen:
- Vererbung in Java.
- Abstrakte Methoden und Klassen in Java
Welches Problem löst die Factory-Methode?
Bei allen Fabrikdesignmustern gibt es zwei Arten von Teilnehmern: Schöpfer (die Fabriken selbst) und Produkte (die von den Fabriken geschaffenen Objekte). Stellen Sie sich die folgende Situation vor: Wir haben eine Fabrik, die Autos der Marke CodeGym produziert. Es weiß, wie man Automodelle mit verschiedenen Karosserietypen erstellt:- Limousinen
- Kombis
- Coupés
- CodeGym-Limousinen
- CodeGym-Kombis
- CodeGym-Coupés
- OneAuto-Limousinen
- OneAuto Kombis
- OneAuto-Coupés
Ein bisschen über das Fabrikmuster
Ich möchte Sie daran erinnern, dass wir zuvor ein kleines virtuelles Café aufgebaut haben. Mithilfe einer einfachen Fabrik lernten wir, wie man verschiedene Kaffeesorten herstellt. Heute werden wir dieses Beispiel noch einmal überarbeiten. Erinnern wir uns daran, wie unser Café mit seiner einfachen Fabrik aussah. Wir hatten einen Kaffeekurs:
public class Coffee {
public void grindCoffee(){
// Grind the coffee
}
public void makeCoffee(){
// Brew the coffee
}
public void pourIntoCup(){
// Pour into a cup
}
}
Und mehrere Kinderkurse, die sich auf bestimmte Kaffeesorten beziehen, die unsere Fabrik produzieren könnte:
public class Americano extends Coffee {}
public class Cappuccino extends Coffee {}
public class CaffeLatte extends Coffee {}
public class Espresso extends Coffee {}
Wir haben eine Enumeration erstellt, um das Aufgeben von Bestellungen zu vereinfachen:
public enum CoffeeType {
ESPRESSO,
AMERICANO,
CAFFE_LATTE,
CAPPUCCINO
}
Die Kaffeefabrik selbst sah so aus:
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;
}
}
Und schließlich sah das Café selbst so aus:
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;
}
}
Modernisierung einer einfachen Fabrik
Unser Café läuft sehr gut. So sehr, dass wir über eine Erweiterung nachdenken. Wir wollen einige neue Standorte eröffnen. Wir sind mutig und unternehmungslustig, damit wir keine langweiligen Coffeeshops eröffnen. Wir möchten, dass jeder Shop eine besondere Note hat. Dementsprechend werden wir zunächst zwei Standorte eröffnen: einen italienischen und einen amerikanischen. Diese Änderungen wirken sich nicht nur auf die Inneneinrichtung aus, sondern auch auf das Getränkeangebot:- Im italienischen Coffeeshop verwenden wir ausschließlich italienische Kaffeemarken mit spezieller Mahlung und Röstung.
- Am amerikanischen Standort wird es größere Portionen geben und wir servieren zu jeder Bestellung Marshmallows.
public class Americano extends Coffee {}
public class Cappuccino extends Coffee {}
public class CaffeLatte extends Coffee {}
public class Espresso extends Coffee {}
Aber jetzt haben wir 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 {}
Da wir das aktuelle Geschäftsmodell beibehalten wollen, möchten wir, dass die orderCoffee(CoffeeType type)
Methode möglichst wenig Änderungen erfährt. Schau es dir an:
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;
}
Welche Möglichkeiten haben wir? Nun ja, wir wissen doch schon, wie man eine Fabrik schreibt, oder? Das einfachste, was mir sofort in den Sinn kommt, ist, zwei ähnliche Fabriken zu schreiben und dann die gewünschte Implementierung an den Konstrukteur unseres Cafés zu übergeben. Dadurch ändert sich die Klasse des Coffeeshops nicht. Zuerst müssen wir eine neue Factory-Klasse erstellen, dafür sorgen, dass sie unsere einfache Factory erbt, und dann die createCoffee(CoffeeType type)
Methode überschreiben. Schreiben wir Fabriken für die Herstellung von Kaffee nach italienischer Art und Kaffee nach amerikanischer Art:
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;
}
}
Jetzt können wir die gewünschte Factory-Implementierung an CoffeeShop übergeben. Sehen wir uns an, wie der Code zum Bestellen von Kaffee in verschiedenen Coffeeshops aussehen würde. Zum Beispiel italienischer und amerikanischer Cappuccino:
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);
}
}
Wir haben zwei verschiedene Coffeeshops geschaffen und jedem die gewünschte Fabrik übergeben. Einerseits haben wir unser Ziel erreicht, aber andererseits... Irgendwie kommt das bei den Unternehmern nicht gut an... Lasst uns herausfinden, was falsch ist. Erstens die Fülle an Fabriken. Was? Nun sollen wir für jeden neuen Standort eine eigene Fabrik erstellen und darüber hinaus sicherstellen, dass bei der Einrichtung eines Cafés die entsprechende Fabrik an den Erbauer übergeben wird? Zweitens ist es immer noch eine einfache Fabrik. Nur leicht modernisiert. Aber wir sind hier, um ein neues Muster zu lernen. Drittens: Ist ein anderer Ansatz nicht möglich? Es wäre toll, wenn wir alle Fragen rund um die Kaffeezubereitung in die packen könntenCoffeeShop
Klasse, indem die Prozesse der Kaffeezubereitung und der Auftragsabwicklung miteinander verknüpft werden und gleichzeitig ausreichend Flexibilität für die Zubereitung verschiedener Kaffeesorten erhalten bleibt. Die Antwort lautet: Ja, das können wir. Dies wird als Factory-Methodenentwurfsmuster bezeichnet.
Von einer einfachen Fabrik zu einer Fabrikmethode
Um die Aufgabe möglichst effizient zu lösen:- Wir geben die
createCoffee(CoffeeType type)
Methode an dieCoffeeShop
Klasse zurück. - Wir werden diese Methode abstrakt machen.
- Die
CoffeeShop
Klasse selbst wird abstrakt. - Die
CoffeeShop
Klasse wird Kinderklassen haben.
CoffeeShop
umsetzt . createCoffee(CoffeeType type)
Nun, Schritt für Schritt. Schritt 1. Machen Sie die Coffee
Klasse abstrakt. Wir haben zwei ganze Familien unterschiedlicher Produkte. Dennoch haben der italienische und der amerikanische Kaffee einen gemeinsamen Vorfahren – die Coffee
Klasse. Es wäre angebracht, es abstrakt zu machen:
public abstract class Coffee {
public void makeCoffee(){
// Brew the coffee
}
public void pourIntoCup(){
// Pour into a cup
}
}
Schritt 2. Erstellen Sie CoffeeShop
eine Zusammenfassung mit einer abstrakten 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);
}
Schritt 3. Erstellen Sie ein italienisches Café, das ein Nachkomme des abstrakten Cafés ist. Wir implementieren die createCoffee(CoffeeType type)
darin enthaltene Methode unter Berücksichtigung der Besonderheiten italienischer Rezepte.
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;
}
}
Schritt 4. Dasselbe machen wir für das Café im amerikanischen Stil
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;
}
}
Schritt 5. Schauen Sie sich an, wie amerikanische und italienische Lattes aussehen:
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);
}
}
Glückwunsch. Wir haben gerade das Designmuster der Fabrikmethode am Beispiel unseres Cafés implementiert.
Das Prinzip hinter Fabrikmethoden
Schauen wir uns nun genauer an, was wir haben. Das folgende Diagramm zeigt die resultierenden Klassen. Die grünen Blöcke sind Erstellerklassen und die blauen Blöcke sind Produktklassen. Welche Schlussfolgerungen können wir ziehen?- Alle Produkte sind Implementierungen der abstrakten
Coffee
Klasse. - Alle Ersteller sind Implementierungen der abstrakten
CoffeeShop
Klasse. - Wir sehen zwei parallele Klassenhierarchien:
- Hierarchie der Produkte. Wir sehen italienische Nachkommen und amerikanische Nachkommen
- Hierarchie der Schöpfer. Wir sehen italienische Nachkommen und amerikanische Nachkommen
- Die
CoffeeShop
Oberklasse hat keine Informationen darüber, welches spezifische Produkt (Coffee
) erstellt wird. - Die
CoffeeShop
Oberklasse delegiert die Erstellung eines bestimmten Produkts an seine Nachkommen. - Jeder Nachkomme der
CoffeeShop
Klasse implementiert einecreateCoffee()
Factory-Methode gemäß seinen eigenen spezifischen Merkmalen. Mit anderen Worten: Die Implementierungen der Produzentenklassen bereiten spezifische Produkte basierend auf den Besonderheiten der Produzentenklasse vor.
Struktur einer Factory-Methode
Das obige Diagramm zeigt die allgemeine Struktur des Factory-Methodenmusters. Was ist hier noch wichtig?- Die Creator-Klasse implementiert alle Methoden, die mit Produkten interagieren, mit Ausnahme der Factory-Methode.
- Die abstrakte
factoryMethod()
Methode muss von allen Nachkommen derCreator
Klasse implementiert werden. - Die
ConcreteCreator
Klasse implementiert diefactoryMethod()
Methode, die direkt das Produkt erstellt. - Diese Klasse ist für die Erstellung spezifischer Produkte verantwortlich. Dies ist der einzige Kurs mit Informationen zum Erstellen dieser Produkte.
- Alle Produkte müssen eine gemeinsame Schnittstelle implementieren, dh sie müssen Nachkommen einer gemeinsamen Produktklasse sein. Dies ist notwendig, damit Klassen, die Produkte verwenden, diese als Abstraktionen und nicht als spezifische Implementierungen verarbeiten können.
GO TO FULL VERSION