JavaRush /Java блог /Random UA /Паттерни проектування: AbstractFactory
Professor Hans Noodles
41 рівень

Паттерни проектування: AbstractFactory

Стаття з групи Random UA
Вітання! Сьогодні ми продовжимо вивчати патерни проектування та поговоримо про абстрактну фабрику . Паттерни проектування: AbstractFactory - 1Чим займемося на лекції:
  • обговоримо, що таке абстрактна фабрика та яку проблему даний патерн вирішує;
  • створимо каркас кросплатформної програми для замовлення кави з інтерфейсом користувача;
  • вивчимо інструкцію щодо застосування даного патерну з діаграмою та кодом;
  • як бонус, у лекції захована пасхалка, завдяки якій ти навчишся визначати ім'я операційної системи за допомогою Java та в залежності від результату виконувати ту чи іншу дію.
Для повного розуміння даного патерну тобі необхідно добре розумітися на таких темах:
  • успадкування в Java;
  • абстрактні класи та методи в Java.

Які проблеми вирішує абстрактна фабрика патерн?

Абстрактна фабрика, як і всі фабричні патерни, допомагає нам правильно організувати створення нових об'єктів. З її допомогою ми керуємо "випуском" різних сімейств взаємозалежних об'єктів. Різні сімейства взаємозалежних об'єктів ... Що це? Не хвилюйся: на практиці все простіше, ніж може здатися. Почнемо із того, що може бути сімейством взаємопов'язаних об'єктів? Припустимо, ми розробляємо з тобою стратегію і є кілька бойових одиниць:
  • піхота;
  • кавалерія;
  • лучники.
Ці типи бойових одиниць пов'язані між собою, адже вони несуть службу в одній армії. Ми можемо сказати, що вищезазначені категорії — це сімейство взаємозалежних об'єктів. Із цим розібралися. Але патерн абстрактна фабрика застосовують для організації створення різнихсімейств взаємозалежних об'єктів. Тут також нічого складного. Продовжимо приклад зі стратегією. Вони, зазвичай, є кілька різних протиборчих сторін. У різних сторін бойові одиниці можуть суттєво відрізнятися зовні. Піхотинці, вершники та лучники римської армії — не те саме, що піхотинці, вершники та лучники вікінгів. У рамках стратегії солдати різних армій — це різні сімейства взаємопов'язаних об'єктів. Було б кумедно, якби помилково програміста у лавах римських піхотинців розгулював солдатів у французькому мундирі часів Наполеона, з мушкетом напереваги. Саме для вирішення такої проблеми потрібний шаблон проектування абстрактної фабрики. Ні, не проблеми конфузів подорожей у часі, а створення різних груп взаємозалежних об'єктів. Абстрактна фабрика надає інтерфейс створення всіх існуючих продуктів (об'єктів сімейства). Абстрактна фабрика, як правило, має кілька реалізацій. Кожна з них відповідає за створення продуктів однієї із варіацій. У рамках стратегії ми мали б абстрактну фабрику, яка створює абстрактних піхотинців, лучників і кавалеристів, а також реалізації цієї фабрики. Фабрика, що створює римських легіонерів і, наприклад, фабрика, що створює воїнів карфагену. Абстракція - найважливіший принцип цього патерну. Клієнти фабрики працюють з нею та з продуктами лише через абстрактні інтерфейси. Тому можна не замислюватися над тим, яких воїнів ми зараз створюємо, а передати цей обов'язок якійсь конкретній реалізації абстрактної фабрики. Кожна з них відповідає за створення продуктів однієї із варіацій. У рамках стратегії ми мали б абстрактну фабрику, яка створює абстрактних піхотинців, лучників і кавалеристів, а також реалізації цієї фабрики. Фабрика, що створює римських легіонерів і, наприклад, фабрика, що створює воїнів карфагену. Абстракція - найважливіший принцип цього патерну. Клієнти фабрики працюють з нею та з продуктами лише через абстрактні інтерфейси. Тому можна не замислюватися над тим, яких воїнів ми зараз створюємо, а передати цей обов'язок якійсь конкретній реалізації абстрактної фабрики. Кожна з них відповідає за створення продуктів однієї із варіацій. У рамках стратегії ми мали б абстрактну фабрику, яка створює абстрактних піхотинців, лучників і кавалеристів, а також реалізації цієї фабрики. Фабрика, що створює римських легіонерів і, наприклад, фабрика, що створює воїнів карфагену. Абстракція - найважливіший принцип цього патерну. Клієнти фабрики працюють з нею та з продуктами лише через абстрактні інтерфейси. Тому можна не замислюватися над тим, яких воїнів ми зараз створюємо, а передати цей обов'язок якійсь конкретній реалізації абстрактної фабрики. що створює воїнів карфагену. Абстракція - найважливіший принцип цього патерну. Клієнти фабрики працюють з нею та з продуктами лише через абстрактні інтерфейси. Тому можна не замислюватися над тим, яких воїнів ми зараз створюємо, а передати цей обов'язок якійсь конкретній реалізації абстрактної фабрики. що створює воїнів карфагену. Абстракція - найважливіший принцип цього патерну. Клієнти фабрики працюють з нею та з продуктами лише через абстрактні інтерфейси. Тому можна не замислюватися над тим, яких воїнів ми зараз створюємо, а передати цей обов'язок якійсь конкретній реалізації абстрактної фабрики.

Продовжуємо автоматизувати кав'ярню

Минулої лекціїми вивчабо патерн фабричний метод, за допомогою якого нам вдалося розширити кавовий бізнес та відкрити кілька нових точок з продажу кави. Сьогодні ми продовжимо роботу з модернізації нашої справи. За допомогою патерну абстрактна фабрика ми закладемо фундамент для нового десктопного додатку для замовлення кави онлайн. Коли пишемо додаток для робочого столу, ми завжди повинні думати про кросплатформенність. Наш додаток має працювати і на macOS, і на Windows (спойлер: Linux залишиться тобі як домашнє завдання). Як буде виглядати наша програма? Досить просто: це буде форма, яка складається з текстового поля, поля вибору та кнопки. Якщо ти маєш досвід використання різних операційних систем, ти точно помітив, що на вінді кнопки малюються не так, як на маку. Як, втім, і решта... Отже, почнемо.
  • кнопки;
  • текстові поля;
  • поля для вибору.
Дисклеймер. Всередині кожного інтерфейсу ми могли б визначити методи, як onClick, onValueChangedабо onInputChanged. Тобто. методи, які дозволять нам обробляти різні події (натискання кнопки, введення тексту, вибір значення у полі вибору). Все це свідомо опущено, щоб не перевантажувати приклад і зробити його наочнішим для вивчення фабричного патерну. Давай визначимо абстрактні інтерфейси для наших продуктів:
public interface Button {}
public interface Select {}
public interface TextField {}
Для кожної операційної системи ми повинні створювати елементи інтерфейсу у стилі цієї операційної системи. Ми пишемо для Windows та MacOS. Створимо реалізації під Windows:
public class WindowsButton implements Button {
}

public class WindowsSelect implements Select {
}

public class WindowsTextField implements TextField {
}
Тепер те саме для MacOS:
public class MacButton implements Button {
}

public class MacSelect implements Select {
}

public class MacTextField implements TextField {
}
Чудово. Тепер ми можемо розпочати нашу абстрактну фабрику, яка буде створювати всі існуючі абстрактні типи продуктів:
public interface GUIFactory {

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

}
Чудово. Як бачиш, поки що нічого складного. Далі все також просто. За аналогією з продуктами створюємо різні реалізації нашої фабрики для кожної OS. Почнемо з Windows:
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();
    }
}
Виведення в консоль усередині методів та конструктора додано для подальшої демонстрації роботи. Тепер для 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();
    }
}
Зауваж: кожен метод, згідно з сигнатурою, повертає абстрактний тип. Але всередині способу ми створюємо конкретну реалізацію товару. Це єдине місце, де ми контролюємо створення конкретних екземплярів. Тепер настав час написати клас форми. Це Java-клас, поля якого є елементами інтерфейсу:
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();
    }
}
Конструктор форми передається абстрактна фабрика, яка створює елементи інтерфейсу. Ми передаватимемо в конструктор потрібну реалізацію фабрики, щоб у нас створювалися елементи інтерфейсу під ту чи іншу ОС.
public class Application {
    private OrderCoffeeForm orderCoffeeForm;

    public void drawOrderCoffeeForm() {
        // Определим ім'я операционной системы, получив значення системной проперти через 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();
    }
}
Якщо ми запустимо програму на вінді, отримаємо наступний висновок:

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
На маку висновок буде наступним:

Creating gui factory for macOS
Creating order coffee form
Creating TextField for macOS
Creating Select for macOS
Creating Button for macOS
На лінуксі:

Unknown OS, can't draw form :( 
А ми з тобою робимо наступний висновок. Ми написали каркас для програми з графічним інтерфейсом користувача, в якому створюються рівно ті елементи інтерфейсу, які доречні в даній ОС. Повторимо тезово, що ми створабо:
  • Сімейство продуктів: поле для введення, поле вибору та кнопки.
  • Різні реалізації сімейства даних продуктів, для Windows та macOS.
  • Абстрактна фабрика, всередині якої визначабо інтерфейс для створення наших продуктів.
  • Дві реалізації нашої фабрики, кожна з яких відповідає за створення певної родини продуктів.
  • Форму Java-клас, полями якого є абстрактні елементи інтерфейсу, які ініціалізуються в конструкторі необхідними значеннями за допомогою абстрактної фабрики.
  • Клас застосування. Усередині нього ми створюємо форму, якою передаємо у конструктор потрібну реалізацію нашої фабрики.
Разом: ми реалізували абстрактна фабрика патерн.

Абстрактна фабрика: інструкція із застосування

Абстрактна фабрика - шаблон проектування для управління створенням різних сімейств продуктів без прив'язки до конкретних класів продуктів. Застосовуючи цей шаблон, необхідно:
  1. Визначити самі сімейства продуктів. Припустимо, у нас є їх два:
    • SpecificProductA1,SpecificProductB1
    • SpecificProductA2,SpecificProductB2
  2. Для кожного продукту всередині сімейства можна визначити абстрактний клас (інтерфейс). У нашому випадку це:
    • ProductA
    • ProductB
  3. Всередині кожного сімейства продуктів кожен продукт повинен реалізовувати інтерфейс, визначений на кроці 2.
  4. Створити абстрактну фабрику з методами create для кожного продукту, визначеного на кроці 2. У нашому випадку такими методами будуть:
    • ProductA createProductA();
    • ProductB createProductB();
  5. Створити реалізації абстрактної фабрики те щоб кожна реалізація керувала створенням продуктів однієї родини. Для цього всередині кожної реалізації абстрактної фабрики необхідно реалізувати всі методи create, так щоб усередині них створювалися і поверталися конкретні реалізації продуктів.
Нижче представлена ​​UML діаграма, яка ілюструє описану вище інструкцію: Паттерни проектування: AbstractFactory - 3Тепер напишемо код за даною інструкцією:
// Определим общие интерфейсы продуктов
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();
    }
}

Домашнє завдання

Для закріплення матеріалу ти можеш зробити 2 речі:
  1. Доопрацювати програму для замовлення кави так, щоб вона працювала і на Linux.
  2. Створити власну абстрактну фабрику для випуску юнітів будь-якої стратегії. Це може бути як історична стратегія з реальними арміями, так і фентезі з орками, гномами та ельфами. Головне, щоби тобі було цікаво. Вияви винахідливість, розстав висновки в консоль і отримуй задоволення від вивчення патернів!
Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ