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

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

Статья из группы Java Developer
В книге “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();
    }
}

Я православный монах - Мантийный монах
Моя одежда - Мантия
Я католический монах - Иезуит
Моя одежда - Ряса с капюшоном
Я англиканский монах - Бенедиктинец
Моя одежда - Ряса
Я буддийский монах - Монах
Моя одежда - Кашая
Я индуистский монах - Саньяси
Моя одежда - Почти ничего, тепло же :)
В заключении можно заметить, что Фабричный метод использовал абстрактный класс с нереализованным методом, который реализовывался в субклассах, а Абстрактная фабрика использовала интерфейс, где реализация (в нашем случае создание монаха) происходила в классах, имплементировавших данный интерфейс.
Комментарии (2)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Игорь Петренко Уровень 1
14 апреля 2022
Читается как-то сложновато
Иван Чепурин Уровень 28
23 декабря 2021
Для неподготовленного читателя в последнем абзаце самым понятным окажется выражение в скобках)