Abstract Factory Design Pattern is very highly used design pattern. This pattern encapsulates object creation of multiple classes in a such a way that we can easily handle future changes.

To understand this pattern well, as always we will fist setup stage with one interesting example, try to write code without factory method and then refactor our code to adjust it with the factory implementation. And in the last, we will see benefits of using a factory method.

Our Task:

Our task is to create one “CountryPeople” machine which creates Men, Women, Boys, and Girls of a different nationality. To help us with our solution, we are already provided few classes. So let’s first see them.

Human Interface
public interface Human {
    void talk();
    void walk();
    void work();
    void sleep();
}
Man abstract class
public abstract class Man implements Human{
    private String name = "";
    public Man(String name) {
        this.name = name;
    }

    @Override
    public void talk() {
        System.out.println(" I am a " + this.getClass().getSimpleName() + ", My name is " + this.name + " and I am talking");
    }

    @Override
    public void walk() {
        System.out.println(" I am a " + this.getClass().getSimpleName() + ", My name is " + this.name + " and I am walking");
    }

    @Override
    public void work() {
        System.out.println(" I am a " + this.getClass().getSimpleName() + ", My name is " + this.name + " and I am working");
    }

    @Override
    public void sleep() {
        System.out.println(" I am a " + this.getClass().getSimpleName() + ", My name is " + this.name + " and I am sleeping");
    }
}
Woman abstract class
public class Woman implements Human{
    private String name = "";
    public Woman(String name) {
        this.name = name;
    }
    @Override
    public void talk() {
        System.out.println(" I am a " + this.getClass().getSimpleName() + ", My name is " + this.name + " and I am talking");
    }

    @Override
    public void walk() {
        System.out.println(" I am a " + this.getClass().getSimpleName() + ", My name is " + this.name + " and I am walking");
    }

    @Override
    public void work() {
        System.out.println(" I am a " + this.getClass().getSimpleName() + ", My name is " + this.name + " and I am working");
    }

    @Override
    public void sleep() {
        System.out.println(" I am a " + this.getClass().getSimpleName() + ", My name is " + this.name + " and I am sleeping");
    }
}
Girl abstract class
public class Girl implements Human{
    private String name = "";
    public Girl(String name) {
        this.name = name;
    }
    @Override
    public void talk() {
        System.out.println(" I am a " + this.getClass().getSimpleName() + ", My name is " + this.name + " and I am talking");
    }

    @Override
    public void walk() {
        System.out.println(" I am a " + this.getClass().getSimpleName() + ", My name is " + this.name + " and I am walking");
    }

    @Override
    public void work() {
        System.out.println(" I am a " + this.getClass().getSimpleName() + ", My name is " + this.name + " and I am working");
    }

    @Override
    public void sleep() {
        System.out.println(" I am a " + this.getClass().getSimpleName() + ", My name is " + this.name + " and I am sleeping");
    }
}
Boy abstract class
public class Boy implements Human{
    private String name = "";
    public Boy(String name) {
        this.name = name;
    }

    @Override
    public void talk() {
        System.out.println(" I am a " + this.getClass().getSimpleName() + ", My name is " + this.name + " and I am talking");
    }

    @Override
    public void walk() {
        System.out.println(" I am a " + this.getClass().getSimpleName() + ", My name is " + this.name + " and I am walking");
    }

    @Override
    public void work() {
        System.out.println(" I am a " + this.getClass().getSimpleName() + ", My name is " + this.name + " and I am working");
    }

    @Override
    public void sleep() {
        System.out.println(" I am a " + this.getClass().getSimpleName() + ", My name is " + this.name + " and I am sleeping");
    }
}

And bunch of concrete classes.

public class ChineseBoy extends Boy{
    public ChineseBoy(String name) {
        super(name);
    }
}



public class ChineseGirl extends Girl{
    public ChineseGirl(String name) {
        super(name);
    }
}



public class ChineseMan extends Man{
    public ChineseMan(String name) {
        super(name);
    }
}



public class ChineseWoman extends Woman{
    public ChineseWoman(String name) {
        super(name);
    }
}


public class IndianBoy extends Boy{
    public IndianBoy(String name) {
        super(name);
    }
}




public class IndianGirl extends Girl{
    public IndianGirl(String name) {
        super(name);
    }
}


public class IndianMan extends Man{

    public IndianMan(String name) {
        super(name);
    }
}



public class IndianWoman extends Woman{
    public IndianWoman(String name) {
        super(name);
    }
}

With above classes on our table, below may be the code we write to create different concrete objects.

public class CountryPeople {
    private Man[] men;
    private Woman[] women;
    private Boy[] boys;
    private Girl[] girls;

    private String country;
    private Man[] manArray;

    public CountryPeople0(String country){
        this.country = country;
    }

    public void setupContry() {
        this.men = getManArray();
        this.women = getWomanArray();
        this.boys = getBoyArray();
        this.girls = getGirlArray();
    }

    private Girl[] getGirlArray() {
        if (country.equalsIgnoreCase("china")){
            return new Girl[]{new ChineseGirl("Ah"), new ChineseGirl("Bao"), new ChineseGirl("Chin")};
        }else if( country.equalsIgnoreCase("india") ){
            return new Girl[]{new IndianGirl("Palak"), new IndianGirl("Radhika"), new IndianGirl("Juhi")};
        }else{
            return null;
        }
    }

    private Boy[] getBoyArray() {
        if (country.equalsIgnoreCase("china")){
            return new Boy[]{new ChineseBoy("Cheng"), new ChineseBoy("Daquan"), new ChineseBoy("Fan")};
        }else if( country.equalsIgnoreCase("india") ){
            return new Boy[]{new IndianBoy("Bhavic"), new IndianBoy("Milan"), new IndianBoy("Mithilesh")};
        }else{
            return null;
        }
    }

    private Woman[] getWomanArray() {
        if (country.equalsIgnoreCase("china")){
            return new Woman[]{ new ChineseWoman("CHEN"), new ChineseWoman("CHENGUANG"), new ChineseWoman("CHANGYING")};
        }else if( country.equalsIgnoreCase("india") ){
            return new Woman[]{ new IndianWoman("Ragini"), new IndianWoman("Sonal"), new IndianWoman("Sharada")};
        }else{
            return null;
        }

    }

    public Man[] getManArray() {
        if (country.equalsIgnoreCase("china")){
            return new Man[]{new ChineseMan("AI"), new ChineseMan("AIGUO"), new ChineseMan("BAI")};
        }else if( country.equalsIgnoreCase("india") ){
            return new Man[]{new IndianMan("Ramesh"), new IndianMan("Chirag"), new IndianMan("Zaver")};
        }else{
            return null;
        }
    }


    public void describePeopleInCountry(){
        System.out.println();
        System.out.println();

        Arrays.stream(this.men).forEach((man) -> {
            man.work();
        });

        System.out.println();
        Arrays.stream(this.women).forEach((woman) -> {
            woman.talk();
        });

        System.out.println();
        Arrays.stream(this.girls).forEach((girl) -> {
            girl.sleep();
        });

        System.out.println();
        Arrays.stream(this.boys).forEach((boy) -> {
            boy.walk();
        });
    }


}

Let’s digest our “CountryPeople” class here. It is offering us a way to specify county name of people we want to create. If we provide “china” as country name, our this class would create all the men, women, girls and boys of china. On the other hand, if we provide “india” as country name, our this class would create all the men, women, girls and boys of India. Let’s write one demo class which can show us how this class can be used.

public static void main(String[] args) {
        CountryPeople china = new CountryPeople0("china");
        CountryPeople india = new CountryPeople0("india");

        china.setupContry();
        india.setupContry();

        china.describePeopleInCountry();
        india.describePeopleInCountry();
    }

Doing things this way is not problematic as long as we are 100% sure that we would need not to support people creation of other nationality. But as we all know, only one this is constant in our Software Development field,

Change

So let’s consider this and try to refactor our code in a such a way, we get flexibility in object creation and also keep our code organized. 😉

Starting implementation of Abstract Factory Design Pattern.

Let’s first see the UML diagram of Abstract Factory Design Pattern.

So if we try to map our existing classes with above UML diagram we can clearly see that we are already having Product interface and different AbstractProductXX. Are you able to spot them? Can’t we map our Human interface with Product interface and our abstract Man,Woman, Girl, Boy classes to AbstractProductXX ? And then ChineseMan, IndianMan etc classes can be mapped with ProductA1, ProductB and so on.

Well, now we can say that we are having left portion of UML diagram already ready to be used, but where is our AbstractFactory interface and ConcreteFactory1, ConcreteFactory2 ?

Off course we are not having them yet, so why don’t we create them? Let’s do it…

First, we will try to design HumanFactorywith below declarations.

public interface HumanFactory {
    Man[] createMen();
    Woman[] createWomen();
    Girl[] createGirls();
    Boy[] createBoys();
}

Now we would use this Factory as AbstractFactory and start creating ConcreteFactories

ChineseHumanFactory
public class ChineseHumanFactory implements HumanFactory{
    @Override
    public Man[] createMen() {
        return new Man[]{new ChineseMan("AI"), new ChineseMan("AIGUO"), new ChineseMan("BAI")};
    }

    @Override
    public Woman[] createWomen() {
        return new Woman[]{ new ChineseWoman("CHEN"), new ChineseWoman("CHENGUANG"), new ChineseWoman("CHANGYING")};
    }

    @Override
    public Girl[] createGirls() {
        return new Girl[]{new ChineseGirl("Ah"), new ChineseGirl("Bao"), new ChineseGirl("Chin")};
    }

    @Override
    public Boy[] createBoys() {
        return new Boy[]{new ChineseBoy("Cheng"), new ChineseBoy("Daquan"), new ChineseBoy("Fan")};
    }
}
IndianHumanFactory
public class IndianHumanFactory implements HumanFactory{
    @Override
    public Man[] createMen() {
        return new Man[]{new IndianMan("Ramesh"), new IndianMan("Chirag"), new IndianMan("Zaver")};
    }

    @Override
    public Woman[] createWomen() {
        return new Woman[]{ new IndianWoman("Ragini"), new IndianWoman("Sonal"), new IndianWoman("Sharada")};
    }

    @Override
    public Girl[] createGirls() {
        return new Girl[]{new IndianGirl("Palak"), new IndianGirl("Radhika"), new IndianGirl("Juhi")};
    }

    @Override
    public Boy[] createBoys() {
        return new Boy[]{new IndianBoy("Bhavic"), new IndianBoy("Milan"), new IndianBoy("Mithilesh")};
    }
}

I think we are not having any reason to complain about above concrete Factory classes right? So now let’s refactor our CountryClass a bit so that we can make it receive an instance of HumanFactory implementations and then use that to create Men, Women, Girls, and Boys.

public class CountryPeople {
    private final HumanFactory humanFactory;

    private Man[] men;
    private Woman[] women;
    private Boy[] boys;
    private Girl[] girls;

    public CountryPeople(HumanFactory humanFactory){
        this.humanFactory = humanFactory;
    }

    public void setupContry(){
        this.men = this.humanFactory.createMen();
        this.women = this.humanFactory.createWomen();
        this.boys = this.humanFactory.createBoys();
        this.girls = this.humanFactory.createGirls();
    }

    public void describePeopleInCountry(){
        System.out.println();
        System.out.println();

        Arrays.stream(this.men).forEach((man) -> {
            man.work();
        });

        System.out.println();
        Arrays.stream(this.women).forEach((woman) -> {
            woman.talk();
        });

        System.out.println();
        Arrays.stream(this.girls).forEach((girl) -> {
            girl.sleep();
        });

        System.out.println();
        Arrays.stream(this.boys).forEach((boy) -> {
            boy.walk();
        });
    }
}

As you can see now our code looks a bit cleaner and organized than before, now if we need to create people with different nationality, we can easily just create a factory for that kind of nationality and then create concrete classes from that factory. 🙂

Here is the demo method which utilizes our work so far.

public class Demo {
    public static void main(String[] args) {
        HumanFactory indianHumanFactory = new IndianHumanFactory();
        HumanFactory chineseHumanFactory = new ChineseHumanFactory();

        CountryPeople indian = new CountryPeople(indianHumanFactory);
        CountryPeople chinese = new CountryPeople(chineseHumanFactory);

        indian.setupContry();
        chinese.setupContry();

        indian.describePeopleInCountry();
        chinese.describePeopleInCountry();
    }
}

Hope you have enjoyed this article. 🙂 All the refactored code is in the attached zip. Happy Coding. 🙂

Download Source Code