JavaRush /Blog Java /Random-PL /Wzorce projektowe: AbstractFactory

Wzorce projektowe: AbstractFactory

Opublikowano w grupie Random-PL
Cześć! Dzisiaj będziemy kontynuować studiowanie wzorców projektowych i rozmawiać o abstrakcyjnej fabryce . Wzorce projektowe: AbstractFactory - 1Co będziemy robić podczas wykładu:
  • Porozmawiajmy, czym jest abstrakcyjna fabryka i jaki problem rozwiązuje ten wzór;
  • stworzymy framework wieloplatformowej aplikacji do zamawiania kawy z interfejsem użytkownika;
  • Przeanalizujmy instrukcje korzystania z tego wzorca za pomocą diagramu i kodu;
  • Jako bonus w wykładzie ukryty jest Easter Egg, dzięki któremu nauczysz się określać nazwę systemu operacyjnego za pomocą Javy i w zależności od wyniku wykonasz tę lub inną czynność.
Aby w pełni zrozumieć ten wzorzec, musisz dobrze rozumieć następujące tematy:
  • dziedziczenie w Javie;
  • klasy i metody abstrakcyjne w Javie.

Jakie problemy rozwiązuje abstrakcyjny wzór fabryki?

Fabryka abstrakcyjna, jak wszystkie wzorce fabryczne, pomaga nam w prawidłowej organizacji powstawania nowych obiektów. Za jego pomocą zarządzamy „uwalnianiem” różnych rodzin powiązanych ze sobą obiektów. Różne rodziny powiązanych ze sobą obiektów... Co to jest? Nie martw się: w praktyce wszystko jest prostsze, niż mogłoby się wydawać. Zacznijmy od tego, czym może być rodzina powiązanych obiektów? Załóżmy, że ty i ja opracowujemy strategię, w której znajduje się kilka jednostek bojowych:
  • piechota;
  • kawaleria;
  • łucznicy.
Tego typu jednostki bojowe są ze sobą powiązane, gdyż służą w tej samej armii. Można powiedzieć, że wymienione powyżej kategorie są rodziną powiązanych ze sobą obiektów. To załatwione. Ale abstrakcyjny wzór fabryczny służy do organizowania tworzenia różnych rodzin połączonych ze sobą obiektów. Tutaj też nie ma nic skomplikowanego. Kontynuujmy przykład ze strategią. Zwykle mają kilka różnych przeciwstawnych stron. Jednostki bojowe różnych stron mogą znacznie różnić się wyglądem. Piechota, jeźdźcy i łucznicy armii rzymskiej to nie to samo, co piechota, jeźdźcy i łucznicy Wikingów. W ramach strategii żołnierze różnych armii stanowią różne rodziny wzajemnie powiązanych obiektów. Byłoby zabawnie, gdyby przez pomyłkę programisty żołnierz we francuskim mundurze z czasów Napoleona, z muszkietem w pogotowiu, przechadzał się wśród rzymskiej piechoty. Do rozwiązania takiego problemu potrzebny jest abstrakcyjny wzorzec projektowy fabryki. Nie, nie problemy związane z podróżami w czasie, ale tworzenie różnych grup połączonych ze sobą obiektów. Abstrakcyjna fabryka zapewnia interfejs do tworzenia wszystkich istniejących produktów (obiektów rodziny). Fabryka abstrakcyjna ma zazwyczaj wiele implementacji. Każdy z nich odpowiada za stworzenie produktów jednej z odmian. W ramach strategii mielibyśmy abstrakcyjną fabrykę tworzącą abstrakcyjną piechotę, łuczników i kawalerię, a także wdrożenia tej fabryki. Fabryka tworząca rzymskich legionistów i np. fabryka tworząca wojowników kartagińskich. Abstrakcja jest najważniejszą zasadą tego wzoru. Klienci fabryczni pracują z nim i z produktami wyłącznie poprzez abstrakcyjne interfejsy. Nie musimy zatem zastanawiać się, jakiego rodzaju wojowników aktualnie tworzymy, ale przenieść tę odpowiedzialność na jakąś konkretną realizację abstrakcyjnej fabryki.

Kontynuujemy automatyzację kawiarni

Na ostatnim wykładzie przestudiowaliśmy wzór metody fabrycznej, za pomocą którego udało nam się rozszerzyć biznes kawowy i otworzyć kilka nowych punktów sprzedaży kawy. Dziś będziemy kontynuować prace nad modernizacją naszego przedsiębiorstwa. Wykorzystując abstrakcyjny wzór fabryki, położymy podwaliny pod nową aplikację desktopową do zamawiania kawy online. Pisząc aplikację na komputer stacjonarny, zawsze powinniśmy myśleć o aplikacji wieloplatformowej. Nasza aplikacja powinna działać zarówno na macOS, jak i na Windowsie (spoiler: Linux zostawię Wam jako zadanie domowe). 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, na pewno zauważyłeś, że w systemie Windows przyciski są renderowane inaczej niż na komputerze Mac. Jak wszystko inne... A więc zaczynajmy. W roli rodzin produktów, jak już zapewne zrozumiałeś, będziemy mieli elementy interfejsu graficznego:
  • guziki;
  • pola tekstowe;
  • pola do wyboru.
Zastrzeżenie. W każdym interfejsie możemy zdefiniować metody takie jak onClick, onValueChangedlub onInputChanged. Te. metody, które pozwolą nam obsłużyć różne zdarzenia (kliknięcie przycisku, wpisanie tekstu, wybranie wartości w polu wyboru). Wszystko to zostało celowo pominięte, aby nie przeciążać przykładu i uczynić go bardziej wizualnym do 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 tego systemu operacyjnego. Piszemy dla Windows i MacOS. Stwórzmy implementacje dla Windows:
public class WindowsButton implements Button {
}

public class WindowsSelect implements Select {
}

public class WindowsTextField implements TextField {
}
Teraz to samo dla MacOS:
public class MacButton implements Button {
}

public class MacSelect implements Select {
}

public class MacTextField implements TextField {
}
Świetnie. Teraz możemy uruchomić naszą fabrykę abstrakcyjnych, która stworzy wszystkie istniejące abstrakcyjne typy produktów:
public interface GUIFactory {

    Button createButton();
    TextField createTextField();
    Select createSelect();

}
Doskonały. Jak widać, na razie nic skomplikowanego. Wtedy wszystko jest równie proste. Analogicznie do produktów, dla każdego systemu operacyjnego tworzymy różne wdrożenia naszej fabryki. Zacznijmy od Windowsa:
public class WindowsGUIFactory implements GUIFactory {
    public WindowsGUIFactory() {
        System.out.println("Creating gui factory 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();
    }
}
Dodano dane wyjściowe konsoli wewnątrz metod i konstruktorów, aby dokładniej zademonstrować, jak to działa. Teraz dla systemu macOS:
public class MacGUIFactory implements GUIFactory {
    public MacGUIFactory() {
        System.out.println("Creating gui factory 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();
    }
}
Uwaga: każda metoda zgodnie ze swoją sygnaturą zwraca typ abstrakcyjny. Ale wewnątrz metody tworzymy konkretną implementację produktu. Tylko tutaj mamy kontrolę nad tworzeniem konkretnych instancji. Teraz czas na napisanie klasy formularza. To jest klasa Java, której pola są elementami interfejsu:
public class OrderCoffeeForm {
    private final TextField customerNameTextField;
    private final Select coffeTypeSelect;
    private final Button orderButton;

    public OrderCoffeeForm(GUIFactory factory) {
        System.out.println("Creating order coffee form");
        customerNameTextField = factory.createTextField();
        coffeTypeSelect = factory.createSelect();
        orderButton = factory.createButton();
    }
}
Abstrakcyjna fabryka przekazywana jest do konstruktora formularza, który tworzy elementy interfejsu. Przekażemy wymaganą implementację fabryczną konstruktorowi, abyśmy mogli stworzyć elementy interfejsu dla konkretnego systemu operacyjnego.
public class Application {
    private OrderCoffeeForm orderCoffeeForm;

    public void drawOrderCoffeeForm() {
        // Определим Nazwa операционной системы, получив oznaczający системной проперти через System.getProperty
        String osName = System.getProperty("os.name").toLowerCase();
        GUIFactory guiFactory;

        if (osName.startsWith("win")) { // Для windows
            guiFactory = new WindowsGUIFactory();
        } else if (osName.startsWith("mac")) { // Для mac
            guiFactory = new MacGUIFactory();
        } else {
            System.out.println("Unknown OS, can't draw form :( ");
            return;
        }
        orderCoffeeForm = new OrderCoffeeForm(guiFactory);
    }

    public static void main(String[] args) {
        Application application = new Application();
        application.drawOrderCoffeeForm();
    }
}
Jeśli uruchomimy aplikację w systemie Windows, otrzymamy następujące dane wyjściowe:

Creating gui factory for Windows OS
Creating order coffee form
Creating TextField for Windows OS
Creating Select for Windows OS
Creating Button for Windows OS
Na komputerze Mac wynik będzie następujący:

Creating gui factory for macOS
Creating order coffee form
Creating TextField for macOS
Creating Select for macOS
Creating Button for macOS
W systemie Linux:

Unknown OS, can't draw form :( 
Cóż, ty i ja wyciągamy następujący wniosek. Napisaliśmy framework dla aplikacji GUI, który tworzy dokładnie te elementy interfejsu, które są odpowiednie dla danego systemu operacyjnego. Powtórzmy krótko to, co stworzyliśmy:
  • Rodzina produktów: pole wejściowe, pole wyboru i przycisk.
  • Różne implementacje tej rodziny produktów dla systemów Windows i macOS.
  • Abstrakcyjna fabryka, w ramach której zdefiniowaliśmy interfejs do tworzenia naszych produktów.
  • Dwie realizacje naszej fabryki, z których każda odpowiada za stworzenie konkretnej rodziny produktów.
  • Formularz, klasa Java, której pola są abstrakcyjnymi elementami interfejsu, które są inicjowane w konstruktorze wymaganymi wartościami przy użyciu abstrakcyjnej fabryki.
  • Klasa aplikacji. Tworzymy w nim formularz, za pomocą którego przekazujemy konstruktorowi wymagane wdrożenie naszej fabryki.
Razem: wdrożyliśmy abstrakcyjny wzór fabryki.

Fabryka abstrakcyjna: instrukcja użytkowania

Abstract Factory to wzorzec projektowy do zarządzania tworzeniem różnych rodzin produktów bez przywiązania do konkretnych klas produktów. Korzystając z tego szablonu, musisz:
  1. Zdefiniuj same rodziny produktów. Załóżmy, że mamy dwa z nich:
    • SpecificProductA1,SpecificProductB1
    • SpecificProductA2,SpecificProductB2
  2. Dla każdego produktu w rodzinie zdefiniuj klasę abstrakcyjną (interfejs). W naszym przypadku jest to:
    • ProductA
    • ProductB
  3. W każdej rodzinie produktów każdy produkt musi implementować interfejs zdefiniowany w kroku 2.
  4. Utwórz abstrakcyjną fabrykę z metodami tworzenia dla każdego produktu zdefiniowanymi w kroku 2. W naszym przypadku będą to metody:
    • ProductA createProductA();
    • ProductB createProductB();
  5. Twórz implementacje abstrakcyjnej fabryki tak, aby każda implementacja kontrolowała powstawanie produktów z tej samej rodziny. Aby to zrobić, wewnątrz każdej implementacji abstrakcyjnej fabryki należy zaimplementować wszystkie metody create, tak aby powstały i wróciły w nich konkretne implementacje produktów.
Poniżej znajduje się diagram UML ilustrujący instrukcje opisane powyżej: Wzorce projektowe: AbstractFactory - 3Napiszmy teraz kod tej instrukcji:
// Определим общие интерфейсы продуктов
public interface ProductA {}
public interface ProductB {}

// Создадим различные реализации (семейства) наших продуктов
public class SpecificProductA1 implements ProductA {}
public class SpecificProductB1 implements ProductB {}

public class SpecificProductA2 implements ProductA {}
public class SpecificProductB2 implements ProductB {}

// Создадим абстрактную фабрику
public interface AbstractFactory {
    ProductA createProductA();
    ProductB createProductB();
}

// Создадим реализацию абстрактной фабрики для создания продуктов семейства 1
public class SpecificFactory1 implements AbstractFactory {

    @Override
    public ProductA createProductA() {
        return new SpecificProductA1();
    }

    @Override
    public ProductB createProductB() {
        return new SpecificProductB1();
    }
}

// Создадим реализацию абстрактной фабрики для создания продуктов семейства 1
public class SpecificFactory2 implements AbstractFactory {

    @Override
    public ProductA createProductA() {
        return new SpecificProductA2();
    }

    @Override
    public ProductB createProductB() {
        return new SpecificProductB2();
    }
}

Praca domowa

Aby skonsolidować materiał, możesz zrobić 2 rzeczy:
  1. Ulepsz aplikację do zamawiania kawy, aby działała na Linuksie.
  2. Stwórz własną abstrakcyjną fabrykę, aby produkować jednostki dowolnej strategii. Może to być strategia historyczna z prawdziwymi armiami lub fantazja z orkami, krasnoludami i elfami. Najważniejsze, że uznasz to za interesujące. Bądź kreatywny, wysyłaj pinezki do konsoli i baw się dobrze, ucząc się wzorów!
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION