Cześć! Dzisiaj będziemy kontynuować studiowanie wzorców projektowych i omówimy abstrakcyjny wzorzec fabryczny .
Oto, co omówimy na lekcji:
Teraz napiszemy kod zgodnie z tymi instrukcjami:

- Omówimy, czym jest abstrakcyjna fabryka i jaki problem rozwiązuje ten wzorzec
- Stworzymy szkielet wieloplatformowej aplikacji do zamawiania kawy przez interfejs użytkownika
- Przestudiujemy instrukcje dotyczące korzystania z tego wzorca, w tym przyjrzymy się diagramowi i kodowi
- Jako bonus, ta lekcja zawiera ukryte jajko wielkanocne, które pomoże ci nauczyć się, jak używać Javy do określania nazwy systemu operacyjnego i, w zależności od wyniku, wykonywania jednej lub drugiej czynności.
- dziedziczenie w Javie
- abstrakcyjne klasy i metody w Javie
Jakie problemy rozwiązuje abstrakcyjna fabryka?
Fabryka abstrakcyjna, podobnie jak wszystkie wzorce fabryczne, pomaga nam zapewnić prawidłowe tworzenie nowych obiektów. Używamy go do zarządzania „produkcją” różnych rodzin powiązanych ze sobą obiektów. Różne rodziny powiązanych ze sobą obiektów... Co to znaczy? Nie martw się: w praktyce wszystko jest prostsze, niż mogłoby się wydawać. Zacznijmy od tego, czym może być rodzina połączonych ze sobą obiektów? Załóżmy, że opracowujemy strategię wojskową obejmującą kilka rodzajów jednostek:- piechota
- kawaleria
- łucznicy
Kontynuujmy automatyzację naszej kawiarni
Na ostatniej lekcji, przestudiowaliśmy wzorzec metody fabrycznej. Wykorzystaliśmy to do rozszerzenia naszego biznesu kawowego i otwarcia kilku nowych lokalizacji. Dziś będziemy kontynuować modernizację naszej firmy. Korzystając z abstrakcyjnego wzorca fabrycznego, stworzymy podwaliny pod nową aplikację desktopową do zamawiania kawy online. Pisząc aplikację desktopową, zawsze powinniśmy myśleć o obsłudze wielu platform. Nasza aplikacja musi działać zarówno na systemie macOS, jak i Windows (spoiler: Wsparcie dla Linuksa pozostawiono do wdrożenia jako pracę domową). Jak będzie wyglądać nasza aplikacja? Całkiem proste: będzie to formularz składający się z pola tekstowego, pola wyboru i przycisku. Jeśli masz doświadczenie w korzystaniu z różnych systemów operacyjnych, z pewnością zauważyłeś, że przyciski w systemie Windows są renderowane inaczej niż na komputerze Mac. Jak wszystko inne... Cóż, zaczynajmy.- guziki
- pola tekstowe
- pola wyboru
onClick
, onValueChanged
, lub onInputChanged
. Innymi słowy, moglibyśmy zdefiniować metody, które pozwolą nam obsłużyć różne zdarzenia (naciśnięcie przycisku, wpisanie tekstu, wybranie wartości w polu wyboru). Wszystko to zostało tutaj celowo pominięte, aby nie przeciążać przykładu i uczynić go bardziej zrozumiałym podczas studiowania wzorca fabrycznego. Zdefiniujmy abstrakcyjne interfejsy dla naszych produktów:
public interface Button {}
public interface Select {}
public interface TextField {}
Dla każdego systemu operacyjnego musimy stworzyć elementy interfejsu w stylu systemu operacyjnego. Będziemy pisać kod dla systemów Windows i MacOS. Stwórzmy implementacje dla Windows:
public class WindowsButton implements Button {
}
public class WindowsSelect implements Select {
}
public class WindowsTextField implements TextField {
}
Teraz robimy to samo dla MacOS:
public class MacButton implements Button {
}
public class MacSelect implements Select {
}
public class MacTextField implements TextField {
}
Doskonały. Teraz możemy przejść do naszej abstrakcyjnej fabryki, która stworzy wszystkie dostępne abstrakcyjne typy produktów:
public interface GUIFactory {
Button createButton();
TextField createTextField();
Select createSelect();
}
Wspaniały. Jak widać, nie zrobiliśmy jeszcze nic skomplikowanego. Wszystko, co następuje, jest również proste. Analogicznie do produktów tworzymy różne wdrożenia fabryczne dla każdego systemu operacyjnego. Zacznijmy od Windowsa:
public class WindowsGUIFactory implements GUIFactory {
public WindowsGUIFactory() {
System.out.println("Creating GUIFactory for Windows OS");
}
public Button createButton() {
System.out.println("Creating Button for Windows OS");
return new WindowsButton();
}
public TextField createTextField() {
System.out.println("Creating TextField for Windows OS");
return new WindowsTextField();
}
public Select createSelect() {
System.out.println("Creating Select for Windows OS");
return new WindowsSelect();
}
}
Dodaliśmy trochę danych wyjściowych konsoli wewnątrz metod i konstruktora, aby lepiej zilustrować, co się dzieje. Teraz dla macOS:
public class MacGUIFactory implements GUIFactory {
public MacGUIFactory() {
System.out.println("Creating GUIFactory for macOS");
}
@Override
public Button createButton() {
System.out.println("Creating Button for macOS");
return new MacButton();
}
@Override
public TextField createTextField() {
System.out.println("Creating TextField for macOS");
return new MacTextField();
}
@Override
public Select createSelect() {
System.out.println("Creating Select for macOS");
return new MacSelect();
}
}
Należy zauważyć, że każda sygnatura metody wskazuje, że metoda zwraca typ abstrakcyjny. Ale wewnątrz metod tworzymy konkretne implementacje produktów. To jedyne miejsce, w którym kontrolujemy tworzenie konkretnych instancji. Teraz nadszedł czas, aby napisać klasę dla formularza. To jest klasa Java, której pola są elementami interfejsu:
public class CoffeeOrderForm {
private final TextField customerNameTextField;
private final Select coffeeTypeSelect;
private final Button orderButton;
public CoffeeOrderForm(GUIFactory factory) {
System.out.println("Creating coffee order form");
customerNameTextField = factory.createTextField();
coffeeTypeSelect = factory.createSelect();
orderButton = factory.createButton();
}
}
Fabryka abstrakcyjna tworząca elementy interfejsu jest przekazywana do konstruktora formularza. Konstruktorowi przekażemy niezbędną implementację fabryczną w celu stworzenia elementów interfejsu dla konkretnego systemu operacyjnego.
public class Application {
private CoffeeOrderForm coffeeOrderForm;
public void drawCoffeeOrderForm() {
// Determine the name of the operating system through System.getProperty()
String osName = System.getProperty("os.name").toLowerCase();
GUIFactory guiFactory;
if (osName.startsWith("win")) { // For Windows
guiFactory = new WindowsGUIFactory();
} else if (osName.startsWith("mac")) { // For Mac
guiFactory = new MacGUIFactory();
} else {
System.out.println("Unknown OS. Unable to draw form :(");
return;
}
coffeeOrderForm = new CoffeeOrderForm(guiFactory);
}
public static void main(String[] args) {
Application application = new Application();
application.drawCoffeeOrderForm();
}
}
Jeśli uruchomimy aplikację w systemie Windows, otrzymamy następujące dane wyjściowe:
Creating GUIFactory for Windows OS
Creating coffee order form
Creating TextField for Windows OS
Creating Select for Windows OS
Creating Button for Windows OS
Na komputerze Mac dane wyjściowe będą wyglądać następująco:
Creating GUIFactory for macOS
Creating coffee order form
Creating TextField for macOS
Creating Select for macOS
Creating Button for macOS
W Linuksie:
Unknown OS. Unable to draw form :(
A teraz podsumowujemy. Napisaliśmy szkielet aplikacji opartej na GUI, w której elementy interfejsu są tworzone specjalnie dla odpowiedniego systemu operacyjnego. Zwięźle powtórzymy to, co stworzyliśmy:
- Rodzina produktów składająca się z pola wejściowego, pola wyboru i przycisku.
- Różne implementacje rodziny produktów dla systemów Windows i macOS.
- Abstrakcyjna fabryka, która definiuje interfejs do tworzenia naszych produktów.
- Dwie wdrożenia naszej fabryki, każda odpowiedzialna za stworzenie określonej rodziny produktów.
- Formularz (klasa Java), którego pola są abstrakcyjnymi elementami interfejsu inicjalizowanymi niezbędnymi wartościami w konstruktorze przy użyciu fabryki abstrakcyjnej.
- Klasa aplikacji Wewnątrz tej klasy tworzymy formularz, przekazując jego konstruktorowi pożądaną implementację fabryczną.
Fabryka abstrakcyjna: jak używać
Fabryka abstrakcyjna to wzorzec projektowy służący do zarządzania tworzeniem różnych rodzin produktów bez powiązania z konkretnymi klasami produktów. Korzystając z tego wzoru, musisz:- Zdefiniuj rodziny produktów. Załóżmy, że mamy ich dwóch:
SpecificProductA1
,SpecificProductB1
SpecificProductA2
,SpecificProductB2
- Dla każdego produktu w rodzinie zdefiniuj klasę abstrakcyjną (interfejs). W naszym przypadku mamy:
ProductA
ProductB
- W ramach każdej rodziny produktów każdy produkt musi implementować interfejs zdefiniowany w kroku 2.
- Utwórz abstrakcyjną fabrykę, z metodami tworzenia każdego produktu zdefiniowanymi w kroku 2. W naszym przypadku metodami tymi będą:
ProductA createProductA();
ProductB createProductB();
- Twórz abstrakcyjne implementacje fabryk, tak aby każda implementacja kontrolowała tworzenie produktów z jednej rodziny. Aby to zrobić, wewnątrz każdej implementacji fabryki abstrakcyjnej należy zaimplementować wszystkie metody kreacji, tak aby tworzyły i zwracały konkretne implementacje produktowe.

// Define common product interfaces
public interface ProductA {}
public interface ProductB {}
// Create various implementations (families) of our products
public class SpecificProductA1 implements ProductA {}
public class SpecificProductB1 implements ProductB {}
public class SpecificProductA2 implements ProductA {}
public class SpecificProductB2 implements ProductB {}
// Create an abstract factory
public interface AbstractFactory {
ProductA createProductA();
ProductB createProductB();
}
// Implement the abstract factory in order to create products in family 1
public class SpecificFactory1 implements AbstractFactory {
@Override
public ProductA createProductA() {
return new SpecificProductA1();
}
@Override
public ProductB createProductB() {
return new SpecificProductB1();
}
}
// Implement the abstract factory in order to create products in family 2
public class SpecificFactory2 implements AbstractFactory {
@Override
public ProductA createProductA() {
return new SpecificProductA2();
}
@Override
public ProductB createProductB() {
return new SpecificProductB2();
}
}
Praca domowa
Aby wzmocnić materiał, możesz zrobić 2 rzeczy:- Dopracuj aplikację do zamawiania kawy, aby działała również w systemie Linux.
- Stwórz własną abstrakcyjną fabrykę do produkcji jednostek zaangażowanych w dowolną strategię wojskową. Może to być zarówno historyczna strategia wojskowa z prawdziwymi armiami, jak i fantastyczna z orkami, gnomami i elfami. Ważne jest, aby wybrać coś, co Cię interesuje. Bądź kreatywny, drukuj wiadomości na konsoli i ciesz się nauką o wzorach!
GO TO FULL VERSION