JavaRush /Java Blog /Random EN /Factory Method and Abstract Factory Patterns

Factory Method and Abstract Factory Patterns

Published in the Random EN group
In the book “Head First. Design Patterns” defines these patterns as follows: The Factory Method pattern defines the interface for creating an object, but allows subclasses to choose the class of the instance to be created. Thus, the Factory method delegates the instantiation operation to subclasses. The Abstract Factory pattern provides an interface for creating families of interrelated or interdependent objects without specifying their concrete classes. Let's try to understand this in more detail. Let's say you decide to write a game about people who decide to become... (you need something original and unusual here) monks. We could start with the following. 1) Create a Monk class and child classes (let’s create one first):

public abstract class Monk {

    public abstract void description();
}

public class OrthodoxMonk extends Monk {
    @Override
    public void description() {
        System.out.println("Я православный монах");
    }
}
2) And of course, create a Monastery class, in which you can implement “monastic vows”:

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;
    }
}
Well, let's check the result:

public class Main {
    public static void main(String[] args) {
        Monastery monastery = new Monastery();
        monastery.createMonk("ORTODOX");
        monastery.getMonk().description();
    }
}

Я православный монах
Now if you need to create... a Catholic monk, you will need to A) Create a new class for a Catholic monk:

public class CatholicMonk extends Monk {
    @Override
    public void description() {
        System.out.println("Я католический монах");
    }
}
B) Make changes to the monastery class:

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;
    }
}
and so every time new types of monks are introduced, you will have to create a new class and edit the existing one. What can be done in this case to somehow “encapsulate” our monastery class from changes. You can try using the Factory Method pattern. How it will look A) Let’s leave the monk class as is, except perhaps add an Anglican monk (not only Catholics and Orthodox Christians have monasticism):

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("Я англиканский монах");
    }
}
B) Let's change the monastery class as follows (let's make it and its method abstract). Here we just use the Factory method:

public abstract class Monastery {
    protected abstract Monk createMonk();
}
and create child classes with method implementation:

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();
    }
}
B) Let's check the code

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();
    }
}

Я православный монах
Я католический монах
Я англиканский монах
Those. as we see now, when adding new types of monks, there will be no need to change existing classes, but only if necessary, add new ones (the class of a specific monastery and monk). Perhaps someone has already noticed that the description method, which was from the very beginning in the Monk class, was also Factory :) The definition of the factory method said that our pattern defines the interface for creating an object, but we did not create any interfaces, although we could create the Monastery class as an interface and implement it in specific implementations. This refers to the word “interface” in a broader sense. The definition also said that it allows subclasses to choose the class of the instance they create . Here we just see that subclasses (child classes) implement this method (that is, these powers to create monk objects are delegated to them). Now let's expand our program a little, introduce the possibility that there are different monks in one denomination or another. For example, in Orthodoxy, based on the position of the Orthodox Church on monasteries and monastics (adopted at the Council of Bishops of the Russian Orthodox Church on November 29 - December 2, 2017), we can conclude that there are 2 types of monks: - Lesser schema (mantle). - Schema (great schema). There are also “preparatory stages”, but people are not considered monks (Trudnik, Novice and Ryasophor or Monk), because they do not take monastic vows. Therefore, we do not take them into account. What do we get in this case: A) Monastery Class (to simplify, let’s focus on Orthodox monasticism for now) with the Factory method :

public abstract class Monastery {
    protected abstract Monk createMonk(String type);
}
and a specific monastery

public class OrthodoxMonastery extends Monastery {

    @Override
    protected Monk createMonk(String type) {
        return new OrthodoxMonk(type);
    }
}
B) Let's fix the monk class:

public abstract class Monk {
    String kind;

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

    public abstract void description();
}
and child class:

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

    @Override
    public void description() {
        System.out.println("Я православный монах - " + kind);
    }
}
C) Let's check our code:

public class Main {
    public static void main(String[] args) {
        Monastery monastery = new OrthodoxMonastery();
        monastery.createMonk("Мантийный монах").description();
        monastery.createMonk("Великосхимник").description();
    }
}

Я православный монах - Мантийный монах
Я православный монах — Великосхимник
Thus, by using the Factory Method pattern, we achieved that we do not have to change previously written classes, but also when expanding the images (types) of monks, a minimum of changes in the code is required. Let's check and add all the orders and congregations of Catholic monks :) But it’s better to focus on the 3 most famous, because there are more than 100 of them: 1) Benedictine 2) Jesuit 3) Franciscan To do this, as before with the Orthodox monk, we need to implement a specific class of Catholic monk :

public class CatholicMonk extends Monk {
    public CatholicMonk(String kind) {
        super(kind);
    }

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

public class CatholicMonastery extends Monastery {

    @Override
    protected Monk createMonk(String type) {
        return new CatholicMonk(type);
    }
}
and check the code:

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();
    }
}

Я православный монах - Мантийный монах
Я православный монах - Великосхимник
Я католический монах - Бенедиктинец
Я католический монах - Иезуит
Я католический монах - Францисканец
Let's finish with this pattern. All these types of monks could also be added to the E-num class in advance, but to simplify the code we will do without it. It's time for the Abstract Factory pattern. We have monks, now we could make them clothes, rosaries, etc. Let's start with clothing, that is, if we return to our definition at the beginning, clothing will become a family of interconnected or interdependent objects . Let's start with the problem that each type of monk has different robes. If we also add Buddhist, then they will be completely different :) To do this, we can create a factory interface, the implementations of which would create the necessary clothes. Therefore A) We create a factory for making clothes

public interface MonkFactory {
    Clothing createClothing();
}
and its implementation

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();
    }
}
Well, let’s not forget about the Buddhist monks :)

public class BuddhistMonkFactory implements MonkFactory {

    @Override
    public Clothing createClothing() {
        return new BuddhistClothing();
    }
}
B) Create a clothing class (to simplify, let’s take the key element of monks’ clothing, we won’t go into detail):

public abstract class Clothing {
    private String name;

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

    public String getName() {
        return name;
    }
}
and child classes

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("Кашая");
    }
}
C) Next, we change the classes of the monks so that they have clothes:

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());
    }
}
D) The monastery class contains our Factory method

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);
}
our implementations have not changed

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);
    }
}
D) Check the result:

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();
    }
}

Я православный монах - Мантийный монах
Моя одежда - Мантия
Я католический монах - Иезуит
Моя одежда - Ряса с капюшоном
Я англиканский монах - Бенедиктинец
Моя одежда - Ряса
Я буддийский монах - Монах
Моя одежда - Кашая
The factory that creates clothes has started working well. Now you can add to the factory the production of equipment for monks for successful prayer (rosary beads, etc.). But the question still remains, is it possible to use 2 patterns together? Of course you can :) Let's try to make the final version of our project and add a Hindu monk: A) Factories now create monks sounds like a “star factory” :

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();
    }
}
B) The monastery class + concrete implementations of the Monastery class are not needed, they are implemented by the factory (on the contrary, we could leave them and remove the factories, but in essence they would then simply be instead of factories, only in this case the Monastery would then have to be made an interface, and not abstract class). And add the application class:

public class Application {

    public Monk create(MonkFactory monkFactory, String type) {
        Monk monk = monkFactory.createMonk(type);
        monk.prepare(monkFactory);
        return monk;
    }
}
B) Monks now contain

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);
}
contains a factory method in implementations, which is implemented using a factory:

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());
    }
}
D) And let's check:

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();
    }
}

Я православный монах - Мантийный монах
Моя одежда - Мантия
Я католический монах - Иезуит
Моя одежда - Ряса с капюшоном
Я англиканский монах - Бенедиктинец
Моя одежда - Ряса
Я буддийский монах - Монах
Моя одежда - Кашая
Я индуистский монах - Саньяси
Моя одежда - Почти ничего, тепло же :)
In conclusion, you can note that the Factory method used an abstract class with an unimplemented method, which was implemented in subclasses, and the Abstract Factory used an interface, where the implementation (in our case, creating a monk) occurred in classes that implemented this interface.
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION