JavaRush /Java блог /Random UA /Паттерни Фабричний метод (Factory Method) та Абстрактна Ф...

Паттерни Фабричний метод (Factory Method) та Абстрактна Фабрика (Abstract Factory)

Стаття з групи Random UA
У книзі Head First. Паттерни проектування” дається таке визначення цим патернам: Паттерн Фабричний Метод визначає інтерфейс створення об'єкта, але дозволяє субкласам вибрати клас створюваного екземпляра. Таким чином, Фабричний метод делегує операцію створення екземпляра субкласів. Паттерн Абстрактна Фабрика надає інтерфейс створення сімейств взаємопов'язаних чи взаємозалежних об'єктів без зазначення конкретних класів. Спробуймо розібратися в цьому докладніше. Допустимо Ви вирішабо написати гру про людей, які вирішують стати… (потрібно щось оригінальне та незвичайне тут) ченцями. Можна було б розпочати з наступного. 1) Завести клас Монах (Monk) та дочірні класи (для початку один створимо):
public abstract class Monk {

    public abstract void description();
}
public class OrthodoxMonk extends Monk {
    @Override
    public void description() {
        System.out.println("Я православный монах");
    }
}
2) І звичайно створити клас Монастир (Monastery), в якому можна в реалізувати «чернечі обітниці»:
public class Monastery {
    private Monk monk;

    public void createMonk(String typeName) {
        this.monk = switch (typeName) {
            case "ORTODOX" -> new OrthodoxMonk();
            default -> null;
        };
    }

    public Monk getMonk() {
        return monk;
    }
}
Ну і перевіримо результат:
public class Main {
    public static void main(String[] args) {
        Monastery monastery = new Monastery();
        monastery.createMonk("ORTODOX");
        monastery.getMonk().description();
    }
}
Я православный монах
Тепер якщо там потрібно створити... католицького ченця, то буде потрібно А) Створити новий клас для католицького ченця:
public class CatholicMonk extends Monk {
    @Override
    public void description() {
        System.out.println("Я католический монах");
    }
}
Б) Внести зміни до класу монастиря:
public class Monastery {
    private Monk monk;

    public void createMonk(String typeName) {
        this.monk = switch (typeName) {
            case "ORTODOX" -> new OrthodoxMonk();
            case "CATHOLIC" -> new CatholicMonk();
            default -> null;
        };
    }

    public Monk getMonk() {
        return monk;
    }
}
і так щоразу при внесенні нових типів ченців доведеться створювати новий клас і правити існуючий. Що можна зробити в цьому випадку, щоб якось «інкапуслювати» від змін наш клас монастиря. Можна спробувати застосувати патерн Фабричний метод. Як це буде виглядати А) Клас ченців залишимо як є, додамо хіба що ще англіканського ченця (не тільки у католиків і православних у християнстві є чернецтво):
public abstract class Monk {

    public abstract void description();
}
public class OrthodoxMonk extends Monk {
    @Override
    public void description() {
        System.out.println("Я православный монах");
    }
}
public class CatholicMonk extends Monk {
    @Override
    public void description() {
        System.out.println("Я католический монах");
    }
}
public class AnglicanMonk extends Monk {
    @Override
    public void description() {
        System.out.println("Я англиканский монах");
    }
}
Б) Змінимо клас монастиря в такий спосіб (зробимо його та його метод абстрактним). Тут якраз використовуємо Фабричний метод:
public abstract class Monastery {
    protected abstract Monk createMonk();
}
та створимо дочірні класи з реалізацією методу:
public class OrthodoxMonastery extends Monastery {
    @Override
    protected Monk createMonk() {
        return new OrthodoxMonk();
    }
}
public class CatholicMonastery extends Monastery {
    @Override
    protected Monk createMonk() {
        return new CatholicMonk();
    }
}
public class AnglicanMonastery extends Monastery {
    @Override
    protected Monk createMonk() {
        return new AnglicanMonk();
    }
}
В) Перевіримо код
public class Main {
    public static void main(String[] args) {
        Monastery monastery;

        monastery = new OrthodoxMonastery();
        monastery.createMonk().description();

        monastery = new CatholicMonastery();
        monastery.createMonk().description();

        monastery = new AnglicanMonastery();
        monastery.createMonk().description();
    }
}
Я православный монах
Я католический монах
Я англиканский монах
Тобто. як ми бачимо тепер при додаванні нових типів ченців не потрібно змінювати наявні класи, а лише за необхідності додавати нові (клас конкретного монастиря і ченця). Можливо хтось уже помітив, що метод description, який був із самого початку в класі Monk був також Фабричним як інтерфейс та імплементувати його конкретним реалізаціям. Тут мається на увазі слово «інтерфейс» у ширшому значенні. Також у визначенні було сказано, що дозволяє субкласам вибрати клас створюваного екземпляра . Тут ми бачимо, що субкласи (дочірні класи) і реалізують даний метод (тобто їм делегуються ці повноваження щодо створення об'єктів ченців). Тепер давайте трохи розширимо нашу програму, внесемо можливість того, що ченці бувають різні у тій чи іншій конфесії. Наприклад у православ'ї на підставі положення православної церкви про монастирі та чернецтво (прийнятий на Архієрейському Соборі Російської Православної Церкви 29 листопада - 2 грудня 2017 року) можна зробити висновок, що ченці бувають 2 типів: - Мала схіма (мантія). - Схімництво (велика схима). Також є «запобіжні етапи», але люди при цьому ченцями не вважаються (Трудник, Послушник і Рясофор або Інок), тому що не дають чернечих обітниць. Тому їх не беремо до уваги. Що ж ми отримуємо в цьому випадку: А) Клас Монастиря (для спрощення поки зупинимося на православному чернецтві) з Фабричним методом :
public abstract class Monastery {
    protected abstract Monk createMonk(String type);
}
та конкретний монастир
public class OrthodoxMonastery extends Monastery {

    @Override
    protected Monk createMonk(String type) {
        return new OrthodoxMonk(type);
    }
}
Б) Виправимо клас ченця:
public abstract class Monk {
    String kind;

    public Monk(String kind) {
        this.kind = kind;
    }

    public abstract void description();
}
та дочірній клас:
public class OrthodoxMonk extends Monk {
    public OrthodoxMonk(String kind) {
        super(kind);
    }

    @Override
    public void description() {
        System.out.println("Я православный монах - " + kind);
    }
}
В) Перевіримо наш код:
public class Main {
    public static void main(String[] args) {
        Monastery monastery = new OrthodoxMonastery();
        monastery.createMonk("Мантийный монах").description();
        monastery.createMonk("Великосхимник").description();
    }
}
Я православный монах - Мантийный монах
Я православный монах — Великосхимник
Таким чином ми використавши патерн Фабричний метод, домоглися того, що не доводиться змінювати класи, раніше написані але і при розширенні образів (видів) ченців потрібно мінімум змін у коді. Давайте перевіримо і додамо всі ордени та конгрегації католицьких ченців :) Але краще зупинимося на 3 найвідоміших, тому що їх понад 100: 1) Бенедиктинець 2) Єзуїт 3) Францисканець :
public class CatholicMonk extends Monk {
    public CatholicMonk(String kind) {
        super(kind);
    }

    @Override
    public void description() {
        System.out.println("Я католический монах - " + kind);
    }
}
та клас монастиря:
public class CatholicMonastery extends Monastery {

    @Override
    protected Monk createMonk(String type) {
        return new CatholicMonk(type);
    }
}
та перевіряємо код:
public class Main {
    public static void main(String[] args) {
        Monastery monastery;

        monastery = new OrthodoxMonastery();
        monastery.createMonk("Мантийный монах").description();
        monastery.createMonk("Великосхимник").description();

        monastery = new CatholicMonastery();
        monastery.createMonk("Бенедиктинец").description();
        monastery.createMonk("Иезуит").description();
        monastery.createMonk("Францисканец").description();
    }
}
Я православный монах - Мантийный монах
Я православный монах - Великосхимник
Я католический монах - Бенедиктинец
Я католический монах - Иезуит
Я католический монах - Францисканец
На цьому із цим патерном закінчимо. Всі ці типи ченців можна було б заздалегідь додати до класу E-num, але для спрощення коду обійдемося без цього. Настав час патерну Абстрактна Фабрика. Ченці у нас є, тепер можна було б зробити їм одяг, чотки тощо. Почнемо з одягу, тобто якщо повернуться до нашого визначення на початку сімейством взаємопов'язаних чи взаємозалежних об'єктів якраз і стане одяг. Почнемо з проблеми, яка полягає в тому, що у кожного виду ченця різні шати. Якщо додати буддистського, то взагалі різні будуть :) Для цього ми можемо створити інтерфейс фабрики, реалізації якої створювали б необхідний одяг. Тому А) Створюємо фабрику для виготовлення одягу
public interface MonkFactory {
    Clothing createClothing();
}
та її реалізації
public class OrthodoxMonkFactory implements MonkFactory {

        @Override
    public Clothing createClothing() {
        return new OrtodoxClothing();
    }
}
public class CatholicMonkFactory implements MonkFactory {

    @Override
    public Clothing createClothing() {
        return new CatholicClothing();
    }
}
public class AnglicanMonkFactory implements MonkFactory {

    @Override
    public Clothing createClothing() {
        return new AnglicanClothing();
    }
}
ну і про буддистських ченців не забудемо :)
public class BuddhistMonkFactory implements MonkFactory {

    @Override
    public Clothing createClothing() {
        return new BuddhistClothing();
    }
}
Б) Створюємо клас одягу (для спрощення візьмемо ключовий елемент одягу ченців, не будемо докладно):
public abstract class Clothing {
    private String name;

    public Clothing(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
}
та дочірні класи
public class OrtodoxClothing extends Clothing {
    public OrtodoxClothing() {
        super("Мантия");
    }
}
public class CatholicClothing extends Clothing {
    public CatholicClothing() {
        super("Ряса с капюшоном");
    }
}
public class AnglicanClothing extends Clothing {
    public AnglicanClothing() {
        super("Ряса");
    }
}
public class BuddhistClothing extends Clothing {
    public BuddhistClothing() {
        super("Кашая");
    }
}
В) Далі змінюємо класи ченців, щоб у них був одяг:
public abstract class Monk {
    String kind;
    Clothing clothing;

    public Monk(String kind) {
        this.kind = kind;
    }

    public void setClothing(Clothing clothing) {
        this.clothing = clothing;
    }

    public abstract void description();
}
public class OrthodoxMonk extends Monk {
    public OrthodoxMonk(String kind) {
        super(kind);
    }

    @Override
    public void description() {
        System.out.println("Я православный монах - " + kind);
        System.out.println("Моя одежда - " + clothing.getName());
    }
}
public class CatholicMonk extends Monk {
    public CatholicMonk(String kind) {
        super(kind);
    }

    @Override
    public void description() {
        System.out.println("Я католический монах - " + kind);
        System.out.println("Моя одежда - " + clothing.getName());
    }
}
public class AnglicanMonk extends Monk {
    public AnglicanMonk(String kind) {
        super(kind);
    }

    @Override
    public void description() {
        System.out.println("Я англиканский монах - " + kind);
        System.out.println("Моя одежда - " + clothing.getName());
    }
}
public class BuddhistMonk extends Monk {
    public BuddhistMonk(String kind) {
        super(kind);
    }

    @Override
    public void description() {
        System.out.println("Я буддийский монах - " + kind);
        System.out.println("Моя одежда - " + clothing.getName());
    }
}
Г) У класі монастиря міститься наш Фабричний метод
public abstract class Monastery {

    public Monk create(MonkFactory monkFactory, String type) {
        Monk monk = createMonk(type);
        monk.setClothing(monkFactory.createClothing());
        return monk;
    }

    protected abstract Monk createMonk(String type);
}
реалізації у нас не змінабося
public class OrthodoxMonastery extends Monastery {

    @Override
    protected Monk createMonk(String type) {
        return new OrthodoxMonk(type);
    }
}
public class CatholicMonastery extends Monastery {

    @Override
    protected Monk createMonk(String type) {
        return new CatholicMonk(type);
    }
}
public class AnglicanMonastery extends Monastery {

    @Override
    protected Monk createMonk(String type) {
        return new AnglicanMonk(type);
    }
}
public class BuddhistMonastery extends Monastery {

    @Override
    protected Monk createMonk(String type) {
        return new BuddhistMonk(type);
    }
}
Д) Перевіряємо результат:
public class Main {
    public static void main(String[] args) {
        Monastery monastery;

        monastery = new OrthodoxMonastery();
        monastery.create(new OrthodoxMonkFactory(), "Мантийный монах").description();

        monastery = new CatholicMonastery();
        monastery.create(new CatholicMonkFactory(), "Иезуит").description();

        monastery = new AnglicanMonastery();
        monastery.create(new AnglicanMonkFactory(), "Бенедиктинец").description();

        monastery = new BuddhistMonastery();
        monastery.create(new BuddhistMonkFactory(), "Монах").description();
    }
}
Я православный монах - Мантийный монах
Моя одежда - Мантия
Я католический монах - Иезуит
Моя одежда - Ряса с капюшоном
Я англиканский монах - Бенедиктинец
Моя одежда - Ряса
Я буддийский монах - Монах
Моя одежда - Кашая
Відмінно фабрика, що створює одяг, запрацювала. Тепер до фабрики можна додати виготовлення інвентарю для ченців для успішної молитви (чотки тощо). Але залишається ще питання, а чи можна 2 патерни разом використовувати. Звичайно можна:) Давайте спробуємо зробити заключний варіант нашого проекту і додамо ще індуїстського ченця: А) Фабрики тепер створюють ченців звучить як «фабрика зірок» :
public interface MonkFactory {
    Monk createMonk(String type);
    Clothing createClothing();
}
public class OrthodoxMonkFactory implements MonkFactory {

    @Override
    public Monk createMonk(String type){
        return new OrthodoxMonk(type);
    }

    @Override
    public Clothing createClothing() {
        return new OrtodoxClothing();
    }
}
public class CatholicMonkFactory implements MonkFactory {

    @Override
    public Monk createMonk(String type){
        return new CatholicMonk(type);
    }

    @Override
    public Clothing createClothing() {
        return new CatholicClothing();
    }
}
public class AnglicanMonkFactory implements MonkFactory {

    @Override
    public Monk createMonk(String type){
        return new AnglicanMonk(type);
    }

    @Override
    public Clothing createClothing() {
        return new AnglicanClothing();
    }
}
public class BuddhistMonkFactory implements MonkFactory {

    @Override
    public Monk createMonk(String type){
        return new BuddhistMonk(type);
    }

    @Override
    public Clothing createClothing() {
        return new BuddhistClothing();
    }
}
public class HinduMonkFactory implements MonkFactory {

    @Override
    public Monk createMonk(String type){
        return new HinduMonk(type);
    }

    @Override
    public Clothing createClothing() {
        return new HinduClothing();
    }
}
Б) Клас монастиря + реалізації класу Монастиря конкретні нам не потрібні, їх реалізує фабрика (можна було навпаки їх залишити, а фабрики видалити, але по суті вони були б тоді просто замість фабрик, тільки при цьому Монастир тоді довелося б зробити інтерфейсом, а не абстрактним класом). І додаємо клас програми:
public class Application {

    public Monk create(MonkFactory monkFactory, String type) {
        Monk monk = monkFactory.createMonk(type);
        monk.prepare(monkFactory);
        return monk;
    }
}
В) Ченці тепер утримують
public abstract class Monk {
    String kind;
    Clothing clothing;

    public Monk(String kind) {
        this.kind = kind;
    }

    public void setClothing(Clothing clothing) {
        this.clothing = clothing;
    }

    public abstract void description();

    public abstract void prepare(MonkFactory monkFactory);
}
у собі фабричний метод у реалізаціях, який реалізують за допомогою фабрики:
public class OrthodoxMonk extends Monk {

    public OrthodoxMonk(String kind) {
        super(kind);
    }

    @Override
    public void description() {
        System.out.println("Я православный монах - " + kind);
        System.out.println("Моя одежда - " + clothing.getName());
    }

    @Override
    public void prepare(MonkFactory monkFactory) {
        setClothing(monkFactory.createClothing());
    }
}
public class CatholicMonk extends Monk {
    public CatholicMonk(String kind) {
        super(kind);
    }

    @Override
    public void description() {
        System.out.println("Я католический монах - " + kind);
        System.out.println("Моя одежда - " + clothing.getName());
    }

    @Override
    public void prepare(MonkFactory monkFactory) {
        setClothing(monkFactory.createClothing());
    }
}
public class AnglicanMonk extends Monk {
    public AnglicanMonk(String kind) {
        super(kind);
    }

    @Override
    public void description() {
        System.out.println("Я англиканский монах - " + kind);
        System.out.println("Моя одежда - " + clothing.getName());
    }

    @Override
    public void prepare(MonkFactory monkFactory) {
        setClothing(monkFactory.createClothing());
    }
}
public class BuddhistMonk extends Monk {
    public BuddhistMonk(String kind) {
        super(kind);
    }

    @Override
    public void description() {
        System.out.println("Я буддийский монах - " + kind);
        System.out.println("Моя одежда - " + clothing.getName());
    }

    @Override
    public void prepare(MonkFactory monkFactory) {
        setClothing(monkFactory.createClothing());
    }
}
public class HinduMonk extends Monk {
    public HinduMonk(String kind) {
        super(kind);
    }

    @Override
    public void description() {
        System.out.println("Я индуистский монах - " + kind);
        System.out.println("Моя одежда - " + clothing.getName());
    }

    @Override
    public void prepare(MonkFactory monkFactory) {
        setClothing(monkFactory.createClothing());
    }
}
Г) І перевіримо:
public class Main {
    public static void main(String[] args) {
        Application application = new Application();

        application.create(new OrthodoxMonkFactory(), "Мантийный монах").description();
        application.create(new CatholicMonkFactory(), "Иезуит").description();
        application.create(new AnglicanMonkFactory(), "Бенедиктинец").description();
        application.create(new BuddhistMonkFactory(), "Монах").description();
        application.create(new HinduMonkFactory(), "Саньяси").description();
    }
}
Я православный монах - Мантийный монах
Моя одежда - Мантия
Я католический монах - Иезуит
Моя одежда - Ряса с капюшоном
Я англиканский монах - Бенедиктинец
Моя одежда - Ряса
Я буддийский монах - Монах
Моя одежда - Кашая
Я индуистский монах - Саньяси
Моя одежда - Почти ничего, тепло же :)
Наприкінці можна побачити, що Фабричний метод використовував абстрактний клас з нереалізованим методом, який реалізовувався у субкласах, а Абстрактна фабрика використовувала інтерфейс, де реалізація (у разі створення ченця) відбувалася класах, імплементували даний інтерфейс.
Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ