Wzorzec fabryczny należy do grupy wzorców kreacyjnych, które koncentrują się na procesie tworzenia obiektów. Jego głównym celem jest zapewnienie elastyczności w tworzeniu instancji obiektów, umożliwiając odroczenie decyzji o tym, która klasa zostanie użyta, do podklas lub konkretnych implementacji. Dzięki temu kod klienta pozostaje niezależny od szczegółów implementacji produktów, co ułatwia rozszerzanie systemu i jego utrzymanie.
Kiedy Warto Użyć Wzorca Factory?
Wzorzec Factory jest szczególnie przydatny, gdy:
- Proces tworzenia obiektu jest skomplikowany lub zależy od dynamicznych parametrów.
- Potrzebujemy dekoplowania kodu klienta od konkretnych klas produktów.
- System musi obsługiwać różne typy obiektów w sposób polimorficzny.
- Chcemy przestrzegać zasady otwarte/zamknięte, umożliwiając dodawanie nowych typów bez modyfikacji istniejącego kodu.
Przykładowo, w grach komputerowych Factory może służyć do tworzenia różnych typów jednostek, w zależności od kontekstu, bez zmiany kodu klienta.
Schematy Implementacji Wzorca Factory
Wzorzec Factory można wdrożyć na dwa główne sposoby: jako Metodę Fabrykującą (Factory Method), gdzie abstrakcyjna metoda decyduje o typie obiektu, oraz jako Fabrykę Abstrakcyjną (Abstract Factory), która tworzy rodziny powiązanych obiektów.
Metoda Fabrykująca (Factory Method)
W tej implementacji definiuje się interfejs do tworzenia pojedynczego typu obiektu, ale podklasy decydują o konkretnej klasie. Kluczowa jest abstrakcyjna metoda fabrykująca, implementowana w konkretnych fabrykach. Wzorzec promuje zasadę otwarte/zamknięte, pozwalając na dodawanie nowych typów bez zmian w kodzie klienta. Jest przydatny, gdy tworzenie obiektu zależy od dynamicznych parametrów, np. typu płatności w systemie e-commerce.
Struktura Wzorca w Kodzie
Struktura obejmuje abstrakcyjną fabrykę z metodą create, konkretną fabrykę implementującą tę metodę (często z użyciem switch lub enuma), abstrakcyjny produkt definiujący interfejs oraz konkretne produkty. Klient używa fabryki bez znajomości szczegółów.
Przykład Kodu: Tworzenie Metod Płatności
Własny przykład w Java: fabryka tworzy obiekty płatności (CreditCardPayment, PayPalPayment) na podstawie enuma PaymentType.
// Abstrakcyjna fabryka
abstract class PaymentFactory {
abstract Payment createPayment(PaymentType type);
}
// Konkretna fabryka
public class ConcretePaymentFactory extends PaymentFactory {
@Override
public Payment createPayment(PaymentType type) {
switch (type) {
case CREDIT_CARD:
return new CreditCardPayment("Visa", 1000.0);
case PAYPAL:
return new PayPalPayment("example@email.com", 500.0);
default:
throw new UnsupportedOperationException("Nieznany typ płatności");
}
}
}
// Abstrakcyjny produkt
public abstract class Payment {
private String details;
private double amount;
protected Payment(String details, double amount) {
this.details = details;
this.amount = amount;
}
public String getDetails() { return details; }
public double getAmount() { return amount; }
public abstract void process();
}
// Konkretne produkty
public class CreditCardPayment extends Payment {
public CreditCardPayment(String cardType, double amount) {
super("Karta: " + cardType, amount);
}
@Override
public void process() {
System.out.println("Przetwarzanie płatności kartą: " + getAmount());
}
}
public class PayPalPayment extends Payment {
public PayPalPayment(String email, double amount) {
super("Email: " + email, amount);
}
@Override
public void process() {
System.out.println("Przetwarzanie płatności PayPal: " + getAmount());
}
}
// Enum
public enum PaymentType {
CREDIT_CARD, PAYPAL;
}
// Klient
public class Main {
public static void main(String[] args) {
PaymentFactory factory = new ConcretePaymentFactory();
Payment cardPayment = factory.createPayment(PaymentType.CREDIT_CARD);
cardPayment.process(); // Wyjście: Przetwarzanie płatności kartą: 1000.0
Payment paypalPayment = factory.createPayment(PaymentType.PAYPAL);
paypalPayment.process(); // Wyjście: Przetwarzanie płatności PayPal: 500.0
}
}Analiza i Wyjaśnienie Działania
- Abstrakcyjna fabryka (PaymentFactory): Definiuje metodę
createPayment, abstrakcyjną, umożliwiając klientowi tworzenie płatności bez znajomości konkretnych klas. - Konkretna fabryka (ConcretePaymentFactory): Implementuje
createPaymentz użyciem switch na podstawie PaymentType, tworząc odpowiednie obiekty z predefiniowanymi parametrami. - Abstrakcyjny produkt (Payment): Definiuje interfejs z polami details, amount oraz abstrakcyjną metodą process().
- Konkretne produkty (CreditCardPayment, PayPalPayment): Rozszerzają Payment, implementując specyficzne zachowanie.
- Enum PaymentType: Zapewnia bezpieczne określanie typu.
- Klient (Main): Używa fabryki do tworzenia obiektów, operując na abstrakcyjnym typie Payment.
Analiza: Dekoplowanie pozwala na łatwe dodawanie nowych typów płatności bez zmian w kliencie. Brak walidacji parametrów (np. ujemna kwota) to potencjalny problem.
Zalety
- Dekoplowanie i elastyczność: Klient nie zna konkretnych klas, co ułatwia rozszerzanie i testowanie.
- Polimorfizm: Obiekty traktowane jednolicie.
- Bezpieczeństwo typów: Enum minimalizuje błędy.
Wady i Potencjalne Problemy
- Zwiększona złożoność: Więcej klas i kodu.
- Prostota switch: Przy wielu typach staje się nieporęczny.
- Brak walidacji: Może prowadzić do błędów runtime.
Fabryka Abstrakcyjna (Abstract Factory)
Ta wersja umożliwia tworzenie rodzin powiązanych obiektów bez określania konkretnych klas. Jest bardziej złożona, zapewniając spójność między obiektami, np. w systemach z różnymi motywami GUI. Idealna dla scenariuszy wymagających kompatybilnych grup obiektów.
Struktura Wzorca w Kodzie
Obejmuje abstrakcyjną fabrykę z metodami dla każdej rodziny, konkretne fabryki tworzące spójne produkty, abstrakcyjne produkty dla rodzin oraz konkretne implementacje.
Przykład Kodu: Tworzenie Elementów GUI
Własny przykład: fabryki tworzą przyciski i pola wyboru dla systemów Windows i Mac.
// Abstrakcyjna fabryka
abstract class GUIFactory {
abstract Button createButton();
abstract Checkbox createCheckbox();
}
// Konkretne fabryki
public class WindowsFactory extends GUIFactory {
@Override
public Button createButton() {
return new WindowsButton("Windows Button");
}
@Override
public Checkbox createCheckbox() {
return new WindowsCheckbox("Windows Checkbox");
}
}
public class MacFactory extends GUIFactory {
@Override
public Button createButton() {
return new MacButton("Mac Button");
}
@Override
public Checkbox createCheckbox() {
return new MacCheckbox("Mac Checkbox");
}
}
// Abstrakcyjne produkty
public abstract class Button {
private String label;
protected Button(String label) {
this.label = label;
}
public String getLabel() { return label; }
public abstract void render();
}
public abstract class Checkbox {
private String label;
protected Checkbox(String label) {
this.label = label;
}
public String getLabel() { return label; }
public abstract void render();
}
// Konkretne produkty
public class WindowsButton extends Button {
public WindowsButton(String label) {
super(label);
}
@Override
public void render() {
System.out.println("Renderowanie przycisku Windows: " + getLabel());
}
}
public class MacButton extends Button {
public MacButton(String label) {
super(label);
}
@Override
public void render() {
System.out.println("Renderowanie przycisku Mac: " + getLabel());
}
}
public class WindowsCheckbox extends Checkbox {
public WindowsCheckbox(String label) {
super(label);
}
@Override
public void render() {
System.out.println("Renderowanie checkbox Windows: " + getLabel());
}
}
public class MacCheckbox extends Checkbox {
public MacCheckbox(String label) {
super(label);
}
@Override
public void render() {
System.out.println("Renderowanie checkbox Mac: " + getLabel());
}
}
// Klient
public class Main {
public static void main(String[] args) {
GUIFactory windowsFactory = new WindowsFactory();
Button winButton = windowsFactory.createButton();
Checkbox winCheckbox = windowsFactory.createCheckbox();
winButton.render(); // Wyjście: Renderowanie przycisku Windows: Windows Button
winCheckbox.render(); // Wyjście: Renderowanie checkbox Windows: Windows Checkbox
GUIFactory macFactory = new MacFactory();
Button macButton = macFactory.createButton();
Checkbox macCheckbox = macFactory.createCheckbox();
macButton.render(); // Wyjście: Renderowanie przycisku Mac: Mac Button
macCheckbox.render(); // Wyjście: Renderowanie checkbox Mac: Mac Checkbox
}
}Analiza i Wyjaśnienie Działania
- Abstrakcyjna fabryka (GUIFactory): Definiuje metody dla rodzin (Button, Checkbox).
- Konkretne fabryki (WindowsFactory, MacFactory): Tworzą spójne elementy GUI dla platformy.
- Abstrakcyjne produkty (Button, Checkbox): Definiują interfejsy dla rodzin.
- Konkretne produkty: Implementują specyficzne renderowanie.
- Klient (Main): Używa fabryk do tworzenia kompatybilnych zestawów.
Analiza: Zapewnia spójność, ale dodanie nowego elementu GUI wymaga zmian we wszystkich fabrykach.
Zalety
- Spójność rodzin: Produkty z jednej fabryki są kompatybilne.
- Dekoplowanie i elastyczność: Łatwe dodawanie nowych rodzin.
Wady i Potencjalne Problemy
- Zwiększona złożoność: Wiele abstrakcji i klas.
- Trudności w rozszerzaniu: Dodanie nowego produktu wymaga modyfikacji wszystkich fabryk.
- Overkill dla prostych systemów: Może komplikować kod niepotrzebnie.
Rozszerzanie Systemu
Aby dodać nową platformę (np. Linux), utwórz LinuxFactory z odpowiednimi produktami. Dla nowego elementu (np. Slider), dodaj metodę do GUIFactory i zaimplementuj w każdej fabryce.
Zalety i Wady Wzorca Factory
Zalety
- Centralizacja tworzenia: Ułatwia zarządzanie obiektami.
- Rozszerzalność: Dodawanie nowych typów bez zmian w kliencie.
- Separacja odpowiedzialności: Kod tworzenia oddzielony od użycia.
Wady
- Złożoność: Więcej klas i abstrakcji.
- Wydajność: Może wprowadzać overhead w prostych przypadkach.
Przykłady Zastosowania w Świecie Rzeczywistym
- Gry komputerowe: Tworzenie jednostek lub broni w zależności od poziomu.
- Systemy płatności: Fabryka do różnych metod płatności.
- Frameworki GUI: Tworzenie elementów dla różnych platform (np. Look and Feel w Swing).
- Cloud services: Fabryki do usług AWS, Azure itp.
- Produkcja napojów: Symulacja fabryk lemoniady z różnymi składnikami.
Te przykłady ilustrują praktyczne zastosowanie Factory w elastycznym tworzeniu obiektów.
Wnioski dla Wzorca Factory
Wzorzec Factory zapewnia elastyczność w tworzeniu obiektów, dekoplowanie i łatwe rozszerzanie systemów. Metoda Fabrykująca nadaje się do pojedynczych typów, a Fabryka Abstrakcyjna do rodzin produktów. Zalety jak centralizacja przeważają nad wadami jak zwiększona złożoność w złożonych aplikacjach. Łączy się dobrze z innymi wzorcami, np. Strategy, dla dynamicznych zachowań.