CodeGym/Blog Java/Random-PL/Wzorce projektowe: abstrakcyjna fabryka
Autor
Alex Vypirailenko
Java Developer at Toshiba Global Commerce Solutions

Wzorce projektowe: abstrakcyjna fabryka

Opublikowano w grupie Random-PL
Cześć! Dzisiaj będziemy kontynuować studiowanie wzorców projektowych i omówimy abstrakcyjny wzorzec fabryczny . Wzorce projektowe: Fabryka abstrakcyjna - 1Oto, co omówimy na lekcji:
  • 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.
Aby w pełni zrozumieć ten wzorzec, musisz być dobrze zorientowany w następujących tematach:
  • 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
Tego typu jednostki są ze sobą powiązane, ponieważ służą w tej samej armii. Można powiedzieć, że wymienione powyżej kategorie to rodzina powiązanych ze sobą obiektów. Rozumiemy to. Ale abstrakcyjny wzorzec fabryczny służy do organizowania tworzenia różnych rodzin połączonych ze sobą obiektów. Tu też nie ma nic skomplikowanego. Kontynuujmy przykład strategii wojskowej. Ogólnie rzecz biorąc, jednostki wojskowe należą do kilku różnych walczących stron. W zależności od tego, po której stronie się znajdują, jednostki wojskowe mogą znacznie różnić się wyglądem. Piechurzy, jeźdźcy i łucznicy armii rzymskiej to nie to samo, co wikingowie, jeźdźcy i łucznicy. W strategii wojskowej żołnierze różnych armii to różne rodziny powiązanych ze sobą obiektów. Byłoby zabawnie, gdyby programista błąd spowodował, że wśród szeregów rzymskiej piechoty można było spotkać żołnierza we francuskim mundurze z epoki napoleońskiej, z muszkietem w pogotowiu. Abstrakcyjny wzorzec projektowania fabryki jest potrzebny właśnie do rozwiązania tego problemu. Nie, nie problem zażenowania, jakie może wywołać podróż w czasie, ale problem tworzenia różnych grup powiązanych ze sobą obiektów. Fabryka abstrakcyjna zapewnia interfejs do tworzenia wszystkich dostępnych produktów (rodziny obiektów). Fabryka abstrakcyjna zazwyczaj ma wiele implementacji. Każdy z nich odpowiada za tworzenie produktów jednej z rodzin. Nasza strategia wojskowa obejmowałaby abstrakcyjną fabrykę, która tworzy abstrakcyjnych żołnierzy piechoty, łuczników i kawalerzystów, a także implementacje tej fabryki. Na przykład, fabryka, która tworzy rzymskich legionistów i fabryka, która tworzy żołnierzy kartagińskich. Najważniejszą zasadą przewodnią tego wzoru jest abstrakcja. Klienci fabryki współpracują z fabryką i jej produktami tylko poprzez abstrakcyjne interfejsy. Dzięki temu nie musisz zastanawiać się, którzy żołnierze są aktualnie tworzeni. Zamiast tego przekazujesz tę odpowiedzialność jakiejś konkretnej implementacji abstrakcyjnej fabryki.

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
Zastrzeżenie: W każdym interfejsie możemy zdefiniować metody takie jak 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ą.
W rezultacie zaimplementowaliśmy abstrakcyjny wzorzec fabryczny.

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:
  1. Zdefiniuj rodziny produktów. Załóżmy, że mamy ich dwóch:
    • SpecificProductA1,SpecificProductB1
    • SpecificProductA2,SpecificProductB2
  2. Dla każdego produktu w rodzinie zdefiniuj klasę abstrakcyjną (interfejs). W naszym przypadku mamy:
    • ProductA
    • ProductB
  3. W ramach każdej rodziny produktów każdy produkt musi implementować interfejs zdefiniowany w kroku 2.
  4. 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();
  5. 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.
Poniższy diagram UML ilustruje powyższe instrukcje: Wzorce projektowe: Fabryka abstrakcyjna - 3Teraz napiszemy kod zgodnie z tymi instrukcjami:
// 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:
  1. Dopracuj aplikację do zamawiania kawy, aby działała również w systemie Linux.
  2. 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!
Komentarze
  • Popularne
  • Najnowsze
  • Najstarsze
Musisz się zalogować, aby dodać komentarz
Ta strona nie ma jeszcze żadnych komentarzy