Hallo! Heute werden wir weiterhin Entwurfsmuster untersuchen und über die Fabrikmethode (FactoryMethod) sprechen. Sie erfahren, was es ist und für welche Aufgaben diese Vorlage geeignet ist. Wir werden uns dieses Entwurfsmuster in der Praxis ansehen und seine Struktur untersuchen. Um Ihnen all das zu verdeutlichen, 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 Teilnehmergruppen: Schöpfer (die Fabriken selbst) und Produkte (die von den Fabriken geschaffenen Objekte). Stellen Sie sich die Situation vor: Wir haben eine Fabrik, die Autos unter der Marke AutoRush produziert. Sie weiß, wie man Automodelle mit verschiedenen Karosserietypen erstellt:- Limousinen
- Kombis
- Coupe
- AutoRush-Limousinen
- AutoRush-Kombis
- Coupé AutoRush
- OneAuto-Limousinen
- OneAuto Kombis
- Coupé OneAuto
Ein wenig über die Fabrikvorlage
Ich möchte Sie daran erinnern: Wir haben mit Ihnen ein kleines virtuelles Café aufgebaut. Darin haben wir gelernt, wie man mit einer einfachen Fabrik verschiedene Kaffeesorten herstellt. Heute werden wir dieses Beispiel verfeinern. Erinnern wir uns daran, wie unser Café mit einer einfachen Fabrik aussah. Wir hatten einen Kaffeekurs:public class Coffee {
public void grindCoffee(){
// перемалываем кофе
}
public void makeCoffee(){
// делаем кофе
}
public void pourIntoCup(){
// наливаем в чашку
}
}
Und auch einige seiner Erben – spezifische Kaffeesorten, 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 {}
Um die Annahme von Bestellungen zu vereinfachen, haben wir Überweisungen eingeführt:
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 das Café selbst:
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("Вот ваш кофе! Спасибо, приходите еще!");
return coffee;
}
}
Modernisierung einer einfachen Fabrik
Unserem Café geht es gut. So sehr, dass wir über eine Erweiterung nachdenken. Wir möchten mehrere neue Punkte eröffnen. Als unternehmungslustige Leute werden wir keine eintönigen Coffeeshops eröffnen. Ich möchte, dass jeder seine eigene Wendung hat. Daher eröffnen wir zunächst zwei Punkte: im italienischen und im amerikanischen Stil. Die Änderungen betreffen nicht nur das Interieur, sondern auch die Getränke:- In einem italienischen Café verwenden wir ausschließlich italienische Kaffeemarken mit spezieller Mahlung und Röstung.
- Die amerikanische Portion wird etwas größer sein und zu jeder Bestellung servieren wir geschmolzene Marshmallows – Marshmallows.
public class Americano extends Coffee {}
public class Cappuccino extends Coffee {}
public class CaffeLatte extends Coffee {}
public class Espresso extends Coffee {}
Und es wird 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 unverändert beibehalten möchten, möchten wir, dass die Methode orderCoffee(CoffeeType type)
möglichst wenig Änderungen erfährt. Werfen wir einen Blick darauf:
public Coffee orderCoffee(CoffeeType type) {
Coffee coffee = coffeeFactory.createCoffee(type);
coffee.grindCoffee();
coffee.makeCoffee();
coffee.pourIntoCup();
System.out.println("Вот ваш кофе! Спасибо, приходите еще!");
return coffee;
}
Welche Möglichkeiten haben wir? 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 erforderliche Implementierung im Konstruktor an unser Café zu übergeben. Dann wird sich die Klasse des Coffeeshops nicht ändern. Zuerst müssen wir eine neue Factory-Klasse erstellen, von unserer einfachen Factory erben und diese überschreiben createCoffee (CoffeeType type)
. Schreiben wir Fabriken für die Zubereitung von Kaffee im italienischen und amerikanischen Stil:
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 erforderliche Factory-Implementierung an CoffeeShop übergeben. Sehen wir uns an, wie der Code zum Bestellen von Kaffee in verschiedenen Coffeeshops aussehen würde. Zum Beispiel Cappuccino im italienischen und amerikanischen Stil:
public class Main {
public static void main(String[] args) {
/*
Закажем капучино в итальянском стиле:
1. Создадим фабрику для приготовления итальянского кофе
2. Создадим новую кофейню, передав ей в конструкторе фабрику итальянского кофе
3. Закажем наш кофе
*/
SimpleItalianCoffeeFactory italianCoffeeFactory = new SimpleItalianCoffeeFactory();
CoffeeShop italianCoffeeShop = new CoffeeShop(italianCoffeeFactory);
italianCoffeeShop.orderCoffee(CoffeeType.CAPPUCCINO);
/*
Закажем капучино в американском стиле
1. Создадим фабрику для приготовления американского кофе
2. Создадим новую кофейню, передав ей в конструкторе фабрику американского кофе
3. Закажем наш кофе
*/
SimpleAmericanCoffeeFactory americanCoffeeFactory = new SimpleAmericanCoffeeFactory();
CoffeeShop americanCoffeeShop = new CoffeeShop(americanCoffeeFactory);
americanCoffeeShop.orderCoffee(CoffeeType.CAPPUCCINO);
}
}
Wir haben zwei verschiedene Coffeeshops eingerichtet und jeweils in die gewünschte Fabrik verlegt. Einerseits haben wir unser Ziel erreicht, aber andererseits... Irgendetwas kratzt an der unbändigen Seele des Unternehmers... Lassen Sie uns herausfinden, was los ist. Erstens die Fülle an Fabriken. Ist es möglich, jedes Mal eine eigene Fabrik für einen neuen Punkt zu erstellen und zusätzlich darauf zu achten, dass beim Erstellen eines Cafés die erforderliche Fabrik an den Konstrukteur übergeben wird? Zweitens ist es immer noch eine einfache Fabrik. Nur ein wenig modernisiert. Wir studieren hier immer noch ein neues Muster. Drittens: Ist es nicht möglich, es anders zu machen? Es wäre cool, wenn wir alle Fragen zur Kaffeezubereitung innerhalb des Klassenzimmers lokalisieren könnten CoffeeShop
, indem wir die Prozesse der Kaffeezubereitung und der Auftragsabwicklung verknüpfen und gleichzeitig genügend Flexibilität bewahren könnten, um Kaffee in verschiedenen Stilrichtungen zuzubereiten. Die Antwort lautet: Ja, das können Sie. Dies wird als Factory-Methodenentwurfsmuster bezeichnet.
Von einer einfachen Fabrik zu einer Fabrikmethode
Um das Problem so effizient wie möglich zu lösen, tun wir:createCoffee(CoffeeType type)
Lassen Sie uns die Methode an die Klasse zurückgebenCoffeeShop
.- Machen wir diese Methode abstrakt.
- Die Klasse selbst
CoffeeShop
wird abstrakt. - Die Klasse
CoffeeShop
wird Erben haben.
CoffeeShop
, das eine Methode anwendet createCoffee(CoffeeType type)
, die den besten Traditionen italienischer Baristas entspricht. Also der Reihe nach. Schritt 1. Lassen Sie uns die Klasse Coffee
abstrakt machen. Wir haben jetzt zwei Familien unterschiedlicher Produkte. Italienische und amerikanische Kaffeegetränke haben immer noch einen gemeinsamen Vorfahren: den Coffee
. Es wäre richtig, es abstrakt zu machen:
public abstract class Coffee {
public void makeCoffee(){
// делаем кофе
}
public void pourIntoCup(){
// наливаем в чашку
}
}
Schritt 2. Machen Sie es CoffeeShop
abstrakt, mit einer abstrakten MethodecreateCoffee(CoffeeType type)
public abstract class CoffeeShop {
public Coffee orderCoffee(CoffeeType type) {
Coffee coffee = createCoffee(type);
coffee.makeCoffee();
coffee.pourIntoCup();
System.out.println("Вот ваш кофе! Спасибо, приходите еще!");
return coffee;
}
protected abstract Coffee createCoffee(CoffeeType type);
}
Schritt 3. Erstellen Sie ein italienisches Café, eine Nachfolgeklasse des abstrakten Cafés. Darin implementieren wir die Methode createCoffee(CoffeeType type)
unter Berücksichtigung italienischer Besonderheiten.
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. Machen wir dasselbe für ein 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 wir uns an, wie die Bestellung eines Latte Macchiato im amerikanischen und italienischen Stil aussehen würde:
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);
}
}
Gratuliere dir. Wir haben gerade das Designmuster der Fabrikmethode in unserem Café implementiert.
So funktioniert die Factory-Methode
Schauen wir uns nun genauer an, was wir haben. Das folgende Diagramm zeigt die resultierenden Klassen. Grüne Blöcke sind Erstellerklassen, blaue Blöcke sind Produktklassen. Welche Schlussfolgerungen lassen sich ziehen?- Alle Produkte sind Implementierungen der abstrakten Klasse
Coffee
. - Alle Ersteller sind Implementierungen der abstrakten Klasse
CoffeeShop
. - Wir beobachten 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 Oberklasse
CoffeeShop
verfügt über keine Informationen darüber, welche spezifische Produktimplementierung (Coffee
) erstellt wird. - Eine Oberklasse
CoffeeShop
delegiert die Erstellung eines bestimmten Produkts an seine Nachkommen. - Jede Nachkommenklasse
CoffeeShop
implementiert eine Factory-MethodecreateCoffee()
gemäß ihren Besonderheiten. Mit anderen Worten: Innerhalb der Implementierungen von Erstellerklassen wird eine Entscheidung getroffen, ein bestimmtes Produkt basierend auf den Besonderheiten der Erstellerklasse vorzubereiten.
Struktur der Factory-Methode
Das obige Diagramm zeigt die allgemeine Struktur des Factory-Methodenmusters. Was ist hier noch wichtig?- Die Creator-Klasse enthält Implementierungen aller Methoden, die mit Produkten interagieren, mit Ausnahme der Factory-Methode.
- Eine abstrakte Methode
factoryMethod()
muss von allen Nachkommen der Klasse implementiert werdenCreator
. - Die Klasse
ConcreteCreator
implementiert eine MethodefactoryMethod()
, die direkt ein Produkt erzeugt. - 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 – also Nachkommen einer gemeinsamen Produktklasse sein. Dies ist notwendig, damit Klassen, die Produkte verwenden, diese auf der Ebene von Abstraktionen und nicht auf der Ebene konkreter Implementierungen bearbeiten können.
GO TO FULL VERSION