Abstract Factory Design Pattern in Java

Welcome to the example of the Abstract Factory Design Pattern in Java. The Abstract Factory design pattern belongs to the group of Creational patterns. The Abstract Factory pattern is very similar to the Factory Pattern, with the main difference being that it functions as a factory of factories.

One possible native paraphrase of “Abstract Factory” is “Factory that creates related objects.”

If you are familiar with the factory design pattern in Java, you may observe that we utilize a single Factory class. This class returns different subclasses depending on the given input, and it employs if-else or switch statements to accomplish this. In the Abstract Factory pattern, we eliminate the need for if-else blocks and instead have a separate factory class for each subclass. Additionally, we have an Abstract Factory class that returns the appropriate subclass based on the input factory class. Initially, this may seem confusing, but once you examine the implementation, it becomes quite easy to understand and grasp the subtle differences between the Factory and Abstract Factory patterns. Similar to our previous post on the factory pattern, we will employ the same superclass and subclasses.

Superclasses and subclasses for the Abstract Factory Design Pattern.

One possible paraphrase for “Computer.java” could be “Java file for a computer program.”

package com.scdev.design.model;
 
public abstract class Computer {
     
    public abstract String getRAM();
    public abstract String getHDD();
    public abstract String getCPU();
     
    @Override
    public String toString(){
        return "RAM= "+this.getRAM()+", HDD="+this.getHDD()+", CPU="+this.getCPU();
    }
}

On this page, you will find the PC class written in Java programming language.

package com.scdev.design.model;
 
public class PC extends Computer {
 
    private String ram;
    private String hdd;
    private String cpu;
     
    public PC(String ram, String hdd, String cpu){
        this.ram=ram;
        this.hdd=hdd;
        this.cpu=cpu;
    }
    @Override
    public String getRAM() {
        return this.ram;
    }
 
    @Override
    public String getHDD() {
        return this.hdd;
    }
 
    @Override
    public String getCPU() {
        return this.cpu;
    }
 
}

Java server

package com.scdev.design.model;
 
 
public class Server extends Computer {
 
    private String ram;
    private String hdd;
    private String cpu;
     
    public Server(String ram, String hdd, String cpu){
        this.ram=ram;
        this.hdd=hdd;
        this.cpu=cpu;
    }
    @Override
    public String getRAM() {
        return this.ram;
    }
 
    @Override
    public String getHDD() {
        return this.hdd;
    }
 
    @Override
    public String getCPU() {
        return this.cpu;
    }
 
}

Creating a factory class for every subclass.

To begin with, it is essential to establish an interface or abstract class for the Abstract Factory called ComputerAbstractFactory.java.

package com.scdev.design.abstractfactory;

import com.scdev.design.model.Computer;

public interface ComputerAbstractFactory {

	public Computer createComputer();

}

Take note that the createComputer() function is producing an instance of the Computer superclass. From now on, our factory classes will implement this interface and return the corresponding subclass. Specifically, in the case of PCFactory.java, it will return an instance of the sub-class related to PCs.

package com.scdev.design.abstractfactory;

import com.scdev.design.model.Computer;
import com.scdev.design.model.PC;

public class PCFactory implements ComputerAbstractFactory {

	private String ram;
	private String hdd;
	private String cpu;
	
	public PCFactory(String ram, String hdd, String cpu){
		this.ram=ram;
		this.hdd=hdd;
		this.cpu=cpu;
	}
	@Override
	public Computer createComputer() {
		return new PC(ram,hdd,cpu);
	}

}

We will also create a factory class specifically for the Server subclass, called ServerFactory.java.

package com.scdev.design.abstractfactory;

import com.scdev.design.model.Computer;
import com.scdev.design.model.Server;

public class ServerFactory implements ComputerAbstractFactory {

	private String ram;
	private String hdd;
	private String cpu;
	
	public ServerFactory(String ram, String hdd, String cpu){
		this.ram=ram;
		this.hdd=hdd;
		this.cpu=cpu;
	}
	
	@Override
	public Computer createComputer() {
		return new Server(ram,hdd,cpu);
	}

}

We will now establish a consumer class, ComputerFactory.java, which will serve as the starting point for client classes in order to generate sub-classes.

package com.scdev.design.abstractfactory;

import com.scdev.design.model.Computer;

public class ComputerFactory {

	public static Computer getComputer(ComputerAbstractFactory factory){
		return factory.createComputer();
	}
}

Take note that it is a basic class and the getComputer method accepts a ComputerAbstractFactory argument and returns a Computer object. At this stage, the implementation should become apparent. Now, let’s create a straightforward testing method and observe how the abstract factory is utilized to obtain instances of sub-classes. This will be referred to as TestDesignPatterns.java.

package com.scdev.design.test;

import com.scdev.design.abstractfactory.PCFactory;
import com.scdev.design.abstractfactory.ServerFactory;
import com.scdev.design.factory.ComputerFactory;
import com.scdev.design.model.Computer;

public class TestDesignPatterns {

	public static void main(String[] args) {
		testAbstractFactory();
	}

	private static void testAbstractFactory() {
		Computer pc = com.scdev.design.abstractfactory.ComputerFactory.getComputer(new PCFactory("2 GB","500 GB","2.4 GHz"));
		Computer server = com.scdev.design.abstractfactory.ComputerFactory.getComputer(new ServerFactory("16 GB","1 TB","2.9 GHz"));
		System.out.println("AbstractFactory PC Config::"+pc);
		System.out.println("AbstractFactory Server Config::"+server);
	}
}

The output of the program above will be:

AbstractFactory PC Config::RAM= 2 GB, HDD=500 GB, CPU=2.4 GHz
AbstractFactory Server Config::RAM= 16 GB, HDD=1 TB, CPU=2.9 GHz

This is the class diagram that showcases the implementation of the abstract factory design pattern.

The advantages of using the Abstract Factory Design Pattern are as follows:

  • Abstract Factory design pattern provides approach to code for interface rather than implementation.
  • Abstract Factory pattern is “factory of factories” and can be easily extended to accommodate more products, for example we can add another sub-class Laptop and a factory LaptopFactory.
  • Abstract Factory pattern is robust and avoid conditional logic of Factory pattern.

Examples of the Abstract Factory Design Pattern in use within the JDK.

  • javax.xml.parsers.DocumentBuilderFactory#newInstance()
  • javax.xml.transform.TransformerFactory#newInstance()
  • javax.xml.xpath.XPathFactory#newInstance()
Leave a Reply 0

Your email address will not be published. Required fields are marked *