Design Patterns in Java: The Abstract Factory Pattern
Learn all about the Abstract Factory Design Pattern in Java, a powerful method for structuring and creating classes. Discover the differences from conventional Factory Patterns and see practical examples of applying this design pattern. Enhance your Java programming skills and make your code architecture more efficient.
Abstract Factory
If you’re familiar with the Factory Design Pattern in Java, you’ll notice that we have a single Factory class. This Factory class returns different subclasses based on the provided input and uses if-else or switch statements to achieve this. In the Abstract Factory Pattern, we eliminate the if-else blocks and have a Factory class for each subclass. Then, there is an abstract Factory class that returns the subclass based on the input Factory class. At first, it may seem confusing, but once you see the implementation, it’s really easy to understand and grasp the slight difference between Factory and Abstract Factory Patterns. As in our Factory Pattern post, we use the same superclass and subclass.
Superclass and Subclass for the Abstract Factory Design Pattern
Computer.java
package com.journaldev.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();
}
}
PC.java
package com.journaldev.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;
}
}
Server.java
package com.journaldev.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;
}
}
Factory Class for Each Subclass
First, we need to create an abstract Factory interface or class.
ComputerAbstractFactory.java
package com.journaldev.design.abstractfactory;
import com.journaldev.design.model.Computer;
public interface ComputerAbstractFactory {
public Computer createComputer();
}
Note that the createComputer() method returns an instance of the Computer superclass. Now, our factory classes will implement this interface and return their respective subclass.
PCFactory.java
package com.journaldev.design.abstractfactory;
import com.journaldev.design.model.Computer;
import com.journaldev.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);
}
}
Similarly, we will have a factory class for the Server subclass.
ServerFactory.java
package com.journaldev.design.abstractfactory;
import com.journaldev.design.model.Computer;
import com.journaldev.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);
}
}
Consumer Class
Now, we will create a consumer class that serves as the entry point for client classes to create a subclass.
ComputerFactory.java
package com.journaldev.design.abstractfactory;
import com.journaldev.design.model.Computer;
public class ComputerFactory {
public static Computer getComputer(ComputerAbstractFactory factory){
return factory.createComputer();
}
}
Note that it’s a simple class, and the getComputer method accepts an argument of type ComputerAbstractFactory and returns a Computer object. At this point, the implementation should become clear. Let’s write a simple test method and see how the Abstract Factory is used to obtain an instance of the subclass.
TestDesignPatterns.java
package com.journaldev.design.test;
import com.journaldev.design.abstractfactory.PCFactory;
import com.journaldev.design.abstractfactory.ServerFactory;
import com.journaldev.design.model.Computer;
public class TestDesignPatterns {
public static void main(String[] args) {
testAbstractFactory();
}
private static void testAbstractFactory() {
Computer pc = com.journaldev.design.abstractfactory.ComputerFactory.getComputer(new PCFactory("2 GB","500 GB","2.4 GHz"));
Computer server = com.journaldev.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 above program 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
Advantages of the Abstract Factory Design Pattern
- The Abstract Factory Design Pattern provides an approach to coding for the interface rather than the implementation.
- The Abstract Factory Pattern is a “factory of factories” and can easily be extended to accommodate more products.