JavaRush /Blog Java /Random-PL /Wzorce projektowe: metoda fabryczna

Wzorce projektowe: metoda fabryczna

Opublikowano w grupie Random-PL
Cześć! Dzisiaj będziemy kontynuować studiowanie wzorców projektowych i rozmawiać o Metodzie Fabryki. Wzorce projektowe: Metoda fabryczna - 1Dowiesz się co to jest i do jakich zadań nadaje się ten szablon. Przyjrzymy się temu wzorcowi projektowemu w praktyce i zbadamy jego strukturę. Aby wszystko było dla Ciebie jasne, musisz zrozumieć następujące tematy:
  1. Dziedziczenie w Javie.
  2. Metody i klasy abstrakcyjne w Javie.

Jaki problem rozwiązuje metoda fabryczna?

We wszystkich wzorcach projektowania fabryk występują dwie grupy uczestników – twórcy (same fabryki) i produkty (przedmioty stworzone przez fabryki). Wyobraźmy sobie sytuację: mamy fabrykę produkującą samochody pod marką AutoRush. Potrafi tworzyć modele samochodów z różnymi rodzajami nadwozi:
  • sedany
  • kombi
  • coupe
Wszystko szło nam tak dobrze, że pewnego pięknego dnia przejęliśmy problem OneAuto. Jako rozsądni menadżerowie nie chcemy stracić klientów OneAuto, a naszym zadaniem jest zrestrukturyzować produkcję w taki sposób, abyśmy mogli produkować:
  • Sedan AutoRush
  • Kombi AutoRush
  • coupe AutoRush
  • sedany OneAuto
  • Kombi OneAuto
  • coupe OneAuto
Jak widać zamiast jednej grupy produktów pochodnych pojawiły się dwie, różniące się pewnymi szczegółami. Wzorzec projektowy metody fabrycznej rozwiązuje problem tworzenia różnych grup produktów, każdy o jakiejś specyfice. Zasadę działania tego szablonu rozważymy w praktyce, stopniowo przechodząc od prostych do złożonych, na przykładzie naszej kawiarni, którą stworzyliśmy w jednym z poprzednich wykładów .

Trochę o szablonie fabrycznym

Przypomnę: zbudowaliśmy z Tobą małą wirtualną kawiarnię. Dowiedzieliśmy się w nim, jak tworzyć różne rodzaje kawy za pomocą prostej fabryki. Dzisiaj udoskonalimy ten przykład. Przypomnijmy sobie jak wyglądała nasza kawiarnia z prostą fabryką. Mieliśmy zajęcia z kawy:
public class Coffee {
    public void grindCoffee(){
        // перемалываем кофе
    }
    public void makeCoffee(){
        // делаем кофе
    }
    public void pourIntoCup(){
        // наливаем в чашку
    }
}
A także kilku jego spadkobierców - konkretne rodzaje kawy, które nasza fabryka mogła produkować:
public class Americano extends Coffee {}
public class Cappuccino extends Coffee {}
public class CaffeLatte extends Coffee {}
public class Espresso extends Coffee {}
Dla wygody przyjmowania zamówień wprowadziliśmy przelewy:
public enum CoffeeType {
    ESPRESSO,
    AMERICANO,
    CAFFE_LATTE,
    CAPPUCCINO
}
Sama fabryka kawy wyglądała tak:
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;
    }
}
I na koniec sama kawiarnia:
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;
    }
}

Modernizacja prostej fabryki

Nasza kawiarnia ma się dobrze. Do tego stopnia, że ​​myślimy o rozbudowie. Chcemy otworzyć kilka nowych punktów. Jako przedsiębiorczy ludzie nie będziemy prowadzić monotonnych kawiarni. Chcę, żeby każdy miał swój własny akcent. Dlatego na początek otworzymy dwa punkty: w stylu włoskim i amerykańskim. Zmiany dotkną nie tylko wnętrza, ale także napojów:
  • we włoskiej kawiarni będziemy używać wyłącznie kaw włoskich marek, ze specjalnym mieleniem i paleniem.
  • Porcja amerykańska będzie nieco większa, a do każdego zamówienia będziemy podawać roztopione pianki – marshmallows.
Jedyne, co pozostanie niezmienione, to nasz model biznesowy, który się sprawdził. Jeśli mówimy językiem kodowym, tak się dzieje. Mieliśmy 4 klasy produktów:
public class Americano extends Coffee {}
public class Cappuccino extends Coffee {}
public class CaffeLatte extends Coffee {}
public class Espresso extends Coffee {}
I wychodzi 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 {}
Chcąc zachować dotychczasowy model biznesowy bez zmian, zależy nam na tym, aby metoda orderCoffee(CoffeeType type)uległa jak najmniejszej liczbie zmian. Rzućmy okiem na to:
public Coffee orderCoffee(CoffeeType type) {
    Coffee coffee = coffeeFactory.createCoffee(type);
    coffee.grindCoffee();
    coffee.makeCoffee();
    coffee.pourIntoCup();

    System.out.println("Вот ваш кофе! Спасибо, приходите еще!");
    return coffee;
}
Jakie mamy opcje? Wiemy już jak napisać fabrykę, prawda? Najprostsze co od razu przychodzi na myśl to napisanie dwóch podobnych fabryk, a następnie przekazanie wymaganej implementacji do naszej kawiarni w konstruktorze. Wtedy klasa kawiarni nie ulegnie zmianie. Najpierw musimy utworzyć nową klasę fabryczną, dziedziczyć z naszej prostej fabryki i zastąpić klasę createCoffee (CoffeeType type). Napiszmy fabryki do tworzenia kawy w stylu włoskim i amerykańskim:
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;
    }

}
Teraz możemy przekazać wymaganą implementację fabryki do CoffeeShop. Zobaczmy jak wyglądałby kod zamówienia kawy z różnych kawiarni. Na przykład cappuccino w stylu włoskim i amerykańskim:
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);
    }
}
Stworzyliśmy dwie różne kawiarnie, przenosząc każdą do wymaganej fabryki. Z jednej strony cel osiągnęliśmy, ale z drugiej... Coś drapie niepohamowaną duszę przedsiębiorcy... Zastanówmy się, co jest nie tak. Po pierwsze, obfitość fabryk. Czy można za każdym razem tworzyć własną fabrykę dla nowego punktu i dodatkowo zadbać o to, aby przy tworzeniu kawiarni wymagana fabryka została przekazana konstruktorowi? Po drugie, jest to nadal prosta fabryka. Tylko trochę zmodernizowany. Wciąż studiujemy tutaj nowy wzór. Po trzecie, czy nie da się tego zrobić inaczej? Byłoby fajnie, gdybyśmy wszystkie pytania dotyczące parzenia kawy mogli zlokalizować w klasie CoffeeShop, łącząc procesy przygotowania kawy i obsługi zamówienia, zachowując jednocześnie elastyczność pozwalającą na przygotowanie kawy w różnych stylach. Odpowiedź brzmi: tak, możesz. Nazywa się to wzorcem projektowym metody fabrycznej.

Od prostej fabryki do metody fabrycznej

Aby rozwiązać problem możliwie najskuteczniej:
  1. Zwróćmy metodę createCoffee(CoffeeType type)do klasy CoffeeShop.
  2. Uczyńmy tę metodę abstrakcyjną.
  3. Sama klasa CoffeeShopstanie się abstrakcyjna.
  4. Klasa CoffeeShopbędzie miała spadkobierców.
Tak przyjacielu. Włoska kawiarnia to nic innego jak spadkobierca klasy CoffeeShop, wdrażający metodę createCoffee(CoffeeType type)zgodną z najlepszymi tradycjami włoskich baristów. A więc po kolei. Krok 1. Uczyńmy klasę Coffeeabstrakcyjną. Obecnie mamy dwie rodziny różnych produktów. Włoskie i amerykańskie napoje kawowe nadal mają wspólnego przodka: Coffee. Poprawne byłoby uczynienie tego abstrakcyjnym:
public abstract class Coffee {
    public void makeCoffee(){
        // делаем кофе
    }
    public void pourIntoCup(){
        // наливаем в чашку
    }
}
Krok 2. Uczyń to CoffeeShopabstrakcyjnym, stosując abstrakcyjną metodęcreateCoffee(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);
}
Krok 3. Stwórz włoską kawiarnię, klasę potomka kawiarni abstrakcyjnej. Wdrażamy w nim metodę createCoffee(CoffeeType type)uwzględniającą włoską specyfikę.
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;
    }
}
Krok 4. Zróbmy to samo dla kawiarni w stylu amerykańskim.
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;
    }
}
Krok 5. Przyjrzyjmy się, jak wyglądałoby zamówienie latte w stylu amerykańskim i włoskim:
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);
    }
}
Gratulacje. Właśnie wdrożyliśmy w naszej kawiarni wzorzec projektowy metody fabrycznej.

Jak działa metoda fabryczna

Przyjrzyjmy się teraz bliżej temu, co otrzymaliśmy. Poniższy diagram przedstawia powstałe klasy. Zielone bloki to klasy twórców, niebieskie bloki to klasy produktów. Wzorce projektowe: Metoda fabryczna - 2Jakie wnioski można wyciągnąć?
  1. Wszystkie produkty są implementacjami klasy abstrakcyjnej Coffee.
  2. Wszyscy kreatorzy są implementacjami klasy abstrakcyjnej CoffeeShop.
  3. Obserwujemy dwie równoległe hierarchie klas:
    • Hierarchia produktów. Widzimy potomków Włochów i potomków Amerykanów
    • Hierarchia twórców. Widzimy potomków Włochów i potomków Amerykanów
  4. Nadklasa CoffeeShopnie posiada informacji o tym, jaka konkretna implementacja produktu ( Coffee) zostanie utworzona.
  5. Nadklasa CoffeeShopdeleguje tworzenie określonego produktu swoim potomkom.
  6. Każda klasa potomna CoffeeShopimplementuje metodę fabryczną createCoffee()zgodnie ze swoją specyfiką. Innymi słowy, w ramach wdrożeń klas twórców podejmowana jest decyzja o przygotowaniu konkretnego produktu w oparciu o specyfikę klasy twórców.
Teraz jesteś gotowy do zdefiniowania wzorca metody fabryki . Wzorzec metody fabrycznej definiuje interfejs do tworzenia obiektu, ale pozwala podklasom wybrać klasę instancji, którą chcesz utworzyć. W związku z tym metoda Factory deleguje operację tworzenia instancji do podklas. Ogólnie rzecz biorąc, zapamiętywanie definicji nie jest tak ważne, jak zrozumienie, jak coś działa.

Struktura metody fabrycznej

Wzorce projektowe: Metoda fabryczna - 3Powyższy diagram przedstawia ogólną strukturę wzorca metody fabrycznej. Co jeszcze jest tutaj ważne?
  1. Klasa Creator zawiera implementacje wszystkich metod wchodzących w interakcję z produktami, z wyjątkiem metody fabrycznej.
  2. Metoda abstrakcyjna factoryMethod()musi zostać zaimplementowana przez wszystkich potomków klasy Creator.
  3. Klasa ConcreteCreatorimplementuje metodę factoryMethod(), która bezpośrednio generuje produkt.
  4. Ta klasa jest odpowiedzialna za tworzenie konkretnych produktów. To jedyne zajęcia, w których znajdują się informacje na temat tworzenia tych produktów.
  5. Wszystkie produkty muszą implementować wspólny interfejs – być potomkami wspólnej klasy produktów. Jest to konieczne, aby klasy korzystające z produktów mogły na nich operować na poziomie abstrakcji, a nie konkretnych implementacji.

Praca domowa

Zatem dzisiaj wykonaliśmy sporo pracy i przestudiowaliśmy wzorzec projektowy metody fabrycznej. Czas utrwalić przerobiony materiał! Zadanie 1. Praca nad otwarciem kolejnej kawiarni. Może być wykonany w stylu angielskim lub hiszpańskim. Lub nawet w stylu statku kosmicznego. Dodajmy do kawy barwnik spożywczy, żeby nabrała blasku i w ogóle kawa będzie po prostu kosmiczna! Zadanie 2. Na ostatnim wykładzie mieliście zadanie stworzenia wirtualnego baru sushi lub wirtualnej pizzerii. Twoim zadaniem nie jest stać w miejscu. Dzisiaj nauczyłeś się, jak możesz wykorzystać wzorzec metody fabrycznej, aby osiągnąć sukces. Czas wykorzystać tę wiedzę i rozwinąć własny biznes ;)
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION