Creational Design Patterns in Java

Creational Design Patterns in Java

Quick Summary:

Creational design patterns in Java encompass Singleton, Factory Method, Abstract Factory, Builder, and Prototype. These patterns provide flexible and structured approaches to object creation, ensuring single instances, creating objects based on interfaces or families, separating construction from representation, and duplicating existing objects. In this blog, you’ll get insight into these design patterns in Java.

Creational design patterns in Java are techniques that offer flexible and efficient ways to create objects. These patterns address various object creation scenarios, such as enforcing a single instance of a class, creating objects based on a common interface, or managing complex object construction. By applying these patterns, developers can improve code reusability, maintainability, and modularity while promoting loose coupling between objects.

Class-creational patterns and Object-creational patterns are two types of this pattern. Inheritance is used efficiently by class-creational patterns in the installation process, while delegation is used effectively by object-creational patterns to get the job done. Let’s explore some commonly used creational design patterns in Java.

Creational Design Patterns in Java include the following patterns:

  • Factory Method
  • Abstract Factory
  • Builder
  • Singleton
  • Object Pool
  • Prototype

Factory Method

The factory design pattern in java is a Creational Design pattern found in JDK and frameworks like Spring and Struts. It is also known as the Factory Method Design Pattern.

The factory design pattern java is used when you have a superclass with multiple subclasses and need to return one of the subclasses based on input. In this technique, the factory class takes over the task of class creation from the client program.

Let’s begin by looking at how to apply the factory design pattern in Java, and then we’ll look at the advantages of doing so. Along with this take a glance at how factory design patterns in Java are employed in JDK.

Let’s Begin!😃

Factory Design Pattern Super Class

An interface can be a superclass of the factory pattern in Java. Whether it’s an abstract class or a regular Java class, the choice is yours.

Factory design pattern in the Java example: we have an abstract superclass with an overridden function toString() native code method for testing our factory design pattern.

package com.aglowid.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();
 } 
}

Factory Design Pattern Sub Class

Let’s say we have two sub-classes PC and server with the below implementation

package com.aglowid.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;
 }
}

Notice that both the classes are extending the Computer class

package com.aglowid.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

Now that we have the superclass and sub-class ready, let’s focus on the factory class. Basic Implementation is as follow:

package com.aglowid.design.factory;
import com.aglowid.design.model.Computer;
import com.aglowid.design.model.PC;
import com.aglowid.design.model.Server;
public class ComputerFactory {
 public static Computer getComputer(String type, String ram, String hdd, String cpu){
  if("PC".equalsIgnoreCase(type)) return new PC(ram, hdd, cpu);
  else if("Server".equalsIgnoreCase(type)) return new Server(ram, hdd, cpu);
  return null;
 }
}

Let’s look at some of the most important aspects of the Factory Design Pattern in Java.

  1. We can either maintain the Factory class as a singleton or make the method that returns the subclass static.
  2. Notice how the different subclasses are built and returned depending on the input parameter. The factory method is getComputer().

Here is the simple factory design pattern in Java example that uses the above factory design pattern implementation.

package com.aglowid.design.test;
import com.aglowid.design.factory.ComputerFactory;
import com.aglowid.design.model.Computer;
public class TestFactory {
 public static void main(String[] args) {
  Computer pc = ComputerFactory.getComputer("pc","4 GB","1 TB","2.4 GHz");
  Computer server = ComputerFactory.getComputer("server","32 GB","2 TB","2.9 GHz");
  System.out.println("Factory PC Config::"+pc);
  System.out.println("Factory Server Config::"+server);
 }
}

Output of the above program:

Factory PC Config::RAM= 4 GB, HDD=1 TB, CPU=2.4 GHz

Factory Server Config::RAM= 32 GB, HDD=2 TB, CPU=2.9 GHz

Advantages of Factory design pattern in Java

  • Factory Design pattern for Java allows you to code for the interface rather than the implementation.
  • The factory design in Java eliminates the need for client code to instantiate actual implementation classes. It makes code more reliable, less linked, and extensible.
  • Through inheritance, the Java factory design pattern provides abstraction between implementation and client classes.

Factory Design Pattern in Java With Realtime Example in JDK

  • util.calender, ResoureBundle, and NumberFormat getInstance() methods use Factory Pattern.
  • Value of() method in wrapper classes like Boolean, Integer, etc.

Singleton Design Pattern in Java

The creational Design patterns in the Java category include singleton design patterns in java , which is one of a gang of four design patterns in Java. It appears to be a relatively easy design pattern, but when it comes to implementation, it raises a lot of questions.

The implementation of singleton design pattern in Java has always been a contentious issue among programmers. Let’s look at singleton in Java, as well as alternative approaches to implementing the singleton design pattern java  

Java singleton design pattern

  • The singleton java design pattern limits the number of times a class can be instantiated and assures that there is only one instance of the class in the JVM.
  • A global access point must be provided by a singleton class to obtain the class’s instance.
  • For logging, driver objects, caching, and thread pools, the singleton design pattern in java is utilized.
  • Other design patterns that use the Singleton design pattern include Abstract Factory, Builder, Prototype, Facade, and others.
  • The singleton design pattern can also be found in core Java classes, such as Java.lang.runtime and Java. awt.Desktop.

There are different approaches of singleton pattern implementation and design concerns with Implementations. Let’s look at each of them in detail.

  • Eager Initialization
  • Static block initialization
  • Lazy initialization
  • Thread-safe singleton
  • Bill Pugh Singleton Implementation
  • Using Reflection to destroy Singleton Pattern
  • Enum Singleton
  • Serialization and Singleton

Eager Initialization

The instance of a Singleton Class is produced now of class loading in eager initialization. This is the simplest technique of creating a singleton class, but it has the disadvantage of creating an instance even if the client application is not using it. The static initialization singleton class is implemented as follows.

package com.aglowid.singleton;
public class EagerInitializedSingleton {
    private static final EagerInitializedSingleton instance = new EagerInitializedSingleton();
    //private constructor to avoid client applications to use constructor
    private EagerInitializedSingleton(){}
    public static EagerInitializedSingleton getInstance(){
        return instance;
    }
}

This is the strategy to employ if your singleton class does not consume a lot of resources. Singleton classes are built for resources such as the file system, database connections, and so on in the majority of instances. Until the client invokes the getInstance method, we should avoid instantiation. Also, there are no exception handling options in this method.

Static block initialization

The implementation of static block initialization is identical to eager initialization, except that a class instance is generated in the static block, which allows for exception handling.

package com.aglowid.singleton;
public class StaticBlockSingleton {
    private static StaticBlockSingleton instance;
    private StaticBlockSingleton(){}
    //static block initialization for exception handling
    static{
        try{
            instance = new StaticBlockSingleton();
        }catch(Exception e){
            throw new RuntimeException("Exception occured in creating singleton instance");
        }
    } 
    public static StaticBlockSingleton getInstance(){
        return instance;
    }
}

Eager initialization and static block initialization both build the instance before it is used, which isn’t the ideal strategy. As a result, we’ll learn how to design a Singleton class that supports lazy initialization in the next sections.

Also Read: – Design Patterns in Java

Lazy Initialization

To implement the singleton design, the lazy Initialization function produces the instance in the global access method. Here’s an example of how to make a singleton class using this method.

package com.aglowid.singleton;
public class LazyInitializedSingleton {
    private static LazyInitializedSingleton instance;
    private LazyInitializedSingleton(){}
    public static LazyInitializedSingleton getInstance(){
        if(instance == null){
            instance = new LazyInitializedSingleton();
        }
        return instance;
    }
}

The above implementation works properly in a single-threaded environment, but it can cause problems in multithreaded systems if many threads are running at the same time inside the if condition. The singleton pattern will be destroyed, and both threads will receive distinct instances of the singleton class. We’ll look at a few different approaches to creating a thread-safe singleton class in the next section.

Thread-safe singleton

Making the global access method synchronised so that only one thread may run it at a time is an easier way to design a thread-safe singleton class. This approach’s general implementation looks like the class below.

package com.aglowid.singleton;
public class ThreadSafeSingleton {
    private static ThreadSafeSingleton instance;
    private ThreadSafeSingleton(){}
    public static synchronized ThreadSafeSingleton getInstance(){
        if(instance == null){
            instance = new ThreadSafeSingleton();
        }
        return instance;
    }
}

The above version is good and offers thread safety, but it degrades speed due to the synchronized method’s cost, even though we only require it for the first few threads that may generate separate instances. The double-checked locking principle is utilized to prevent this unnecessary burden every time. The synchronized block is utilized inside the if condition in this technique, along with an extra check to guarantee that only one instance of a singleton class is produced.

The following code snippet provides the double-checked locking implementation

public static ThreadSafeSingleton getInstanceUsingDoubleLocking(){
    if(instance == null){
        synchronized (ThreadSafeSingleton.class) {
            if(instance == null){
                instance = new ThreadSafeSingleton();
            }
        }
    }
    return instance;
}

Bill Pugh Singleton Implementation

The above procedures used to fail in specific cases where too many threads tried to acquire the instance of the Singleton class at the same time due to difficulties with the Java memory model. As a result, Bill Pugh devised a new method for creating the Singleton class: an inner static helper class. This is how Bill Pugh Singleton’s implementation goes:

package com. aglowid.singleton;
public class BillPughSingleton {
    private BillPughSingleton(){}
    private static class SingletonHelper{
        private static final BillPughSingleton INSTANCE = new BillPughSingleton();
    }
    public static BillPughSingleton getInstance(){
        return SingletonHelper.INSTANCE;
    }
}

The instance of the singleton class is contained in a private inner static class. The SingletonHelper class is not loaded into memory when the singleton class is loaded; it is only loaded when the get instance method is called that this class is loaded and the singleton class instance is created.

Because it does not require synchronization, this is the most generally used technique for singleton classes.

Using Reflection to destroy singleton pattern

All of the above singleton implementation methods can be destroyed using reflection. With the help of an example, we can better comprehend it.

package com.aglowid.singleton;
import Java.lang.reflect.Constructor;
public class ReflectionSingletonTest {
    public static void main(String[] args) {
        EagerInitializedSingleton instanceOne = EagerInitializedSingleton.getInstance();
        EagerInitializedSingleton instanceTwo = null;
        try {
            Constructor[] constructors = EagerInitializedSingleton.class.getDeclaredConstructors();
            for (Constructor constructor : constructors) {
                //Below code will destroy the singleton pattern
                constructor.setAccessible(true);
                instanceTwo = (EagerInitializedSingleton) constructor.newInstance();
                break;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println(instanceOne.hashCode());
        System.out.println(instanceTwo.hashCode());
    }
}

Enum Singleton

To get around this, Joshua Bloch recommends using Enum to build singleton design principles, because Java guarantees that any enum variable is only instantiated once in a Java program. The singleton is also globally available because the Java enum value is. The disadvantage is that the enum type is somewhat rigid. It does not, for example, support slow loading.

package com.aglowid.singleton;

public enum EnumSingleton {

    INSTANCE;
    
    public static void doSomething(){
        //do something
    }
}

Serialization & Singleton

In a distributed system, a singleton class may need to implement a Serializable interface to save the state in the file system and retrieve it at a later time. A singleton class that implements the Serializable interface is described below.

package com. aglowid.singleton;
import Java.io.Serializable;
public class SerializedSingleton implements Serializable{
    private static final long serialVersionUID = -7604766932017737115L;
    private SerializedSingleton(){}
    
    private static class SingletonHelper{
        private static final SerializedSingleton instance = new SerializedSingleton();
    }
    public static SerializedSingleton getInstance(){
        return SingletonHelper.instance;
    }  
}

The issue with serialized singleton classes is that anytime they are deserialized, a new instance of the class is created. Let’s have a look at it using a simple program.

package com.aglowid.singleton;
import Java.io.FileInputStream;
import Java.io.FileNotFoundException;
import Java.io.FileOutputStream;
import Java.io.IOException;
import Java.io.ObjectInput;
import Java.io.ObjectInputStream;
import Java.io.ObjectOutput;
import Java.io.ObjectOutputStream;
public class SingletonSerializedTest {
    public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException {
        SerializedSingleton instanceOne = SerializedSingleton.getInstance();
        ObjectOutput out = new ObjectOutputStream(new FileOutputStream(
                "filename.ser"));
        out.writeObject(instanceOne);
        out.close();
        //deserailize from file to object
        ObjectInput in = new ObjectInputStream(new FileInputStream(
                "filename.ser"));
        SerializedSingleton instanceTwo = (SerializedSingleton) in.readObject();
        in.close();
        System.out.println("instanceOne hashCode="+instanceOne.hashCode());
        System.out.println("instanceTwo hashCode="+instanceTwo.hashCode());
        
    }

}

The output of the above program:

instanceOne hashCode=2011117821
instanceTwo hashCode=109647522

As a result, the singleton pattern is destroyed. To avoid this, all we need to do is implement the readResolve() method.

protected Object readResolve() {
    return getInstance();
}

Prototype Design Pattern In Java

When creating an object is expensive and time-consuming, Java prototype design pattern is utilized. As a result, this design pattern in java allows you to replicate the original object to a new object and then modify it to suit your needs. To replicate the object, this approach employs Java Cloning.

Are you looking to hire java developer?

Get the top-notch web & app solutions from the certified java developers at Aglowid

Prototype Design Pattern Example

With an example, it would be simple to comprehend the prototype design pattern in Java. Assume we have an object that retrieves data from a database. It’s not a smart idea to use the new keyword to construct the object because we’ll need to change the data in the program several times. Cloning the existing object into a new object and performing data manipulation might be a preferable way.

package com.aglowid.design.prototype;
import Java.util.ArrayList;
import Java.util.List;
public class Employees implements Cloneable{

 private List empList;
 
 public Employees(){
  empList = new ArrayList();
 }
 
 public Employees(List list){
  this.empList=list;
 }
 public void loadData(){
  //read all employees from database and put into the list
  empList.add("Peter");
  empList.add("Ronald");
  empList.add("David");
  empList.add("Lisa");
 }
 
 public List getEmpList() {
  return empList;
 }

 @Override
 public Object clone() throws CloneNotSupportedException{
   List temp = new ArrayList();
   for(String s : this.getEmpList()){
    temp.add(s);
   }
   return new Employees(temp);
 }
 
}

The clone method has been overridden in order to offer a deep duplicate of the employee list. The following is an example of a prototype design pattern that demonstrates the benefits of the prototype pattern.

package com.aglowid.design.test;
import Java.util.List;
import com.aglowid.design.prototype.Employees;
public class PrototypePatternTest {
 public static void main(String[] args) throws CloneNotSupportedException {
  Employees emps = new Employees();
  emps.loadData();
  
  //Use the clone method to get the Employee object
  Employees empsNew = (Employees) emps.clone();
  Employees empsNew1 = (Employees) emps.clone();
  List list = empsNew.getEmpList();
  list.add("John");
  List list1 = empsNew1.getEmpList();
  list1.remove("Peter");
  
  System.out.println("emps List: "+emps.getEmpList());
  System.out.println("empsNew List: "+list);
  System.out.println("empsNew1 List: "+list1);
 }

}

The output of the above example is as follows:

emps List: [Peter, Ronald, David, Lisa]
empsNew List: [Peter, Ronald, David, Lisa, John]
empsNew1 List: [Ronald, David, Lisa]

Builder Design Pattern

When an object has a lot of properties, the pattern was created to tackle problems with Factory and abstract factory design patterns. By giving a mechanism to build the object step-by-step and a method that returns a completed object, the Builder Pattern tackles concerns with a high number of optional parameters and inconsistent states.

When the Object has a lot of properties, the Builder Pattern was proposed to overcome some of the concerns with Factory and Abstract Factory design patterns.

When an object has a lot of attributes, there are three key concerns with Factory and Abstract Factory design patterns.

  • Too many arguments to pass from the client application to the Factory class might lead to errors because the types of arguments are usually the same, and it’s difficult to keep track of the arguments on the client-side.
  • Some parameters may be optional, but they are required to send as NULL in the Java factory pattern.
  • When Factory classes are used and the object is large and complicated, it might be perplexing because all of that complexity is also a part of the Factory pattern class in Java.

By giving a function Object with the required argument, we may handle issues with a high number of parameters. The optional parameters are then set using various setter methods. The difficulty with this approach is that unless all the attributes are explicitly set, the object state will be inconsistent.

Builder Design Pattern in Java

Let’s see how to implement the builder design pattern in Java.

  • To begin, you must first create a static nested class, after which you must copy all of the arguments from the outer class to the builder class. We should stick to the naming tradition and name the builder class ComputerBuilder if the class is named after a computer.
  • A public function Object for the Java Builder class should have all of the needed characteristics and parameters.
  • After setting the optional attribute, the Java Builder class should return the same builder object.
  • The next step is to add a build() method to the builder class, which will return the object that the client program requires. We’ll need a private function Object using the Builder class as a parameter in the class to do this.

Following is the sample builder pattern example:

package com.aglowid.design.builder;
public class Computer {
 //required parameters
 private String HDD;
 private String RAM;
 //optional parameters
 private boolean isGraphicsCardEnabled;
 private boolean isBluetoothEnabled;
 public String getHDD() {
  return HDD;
 }
 public String getRAM() {
  return RAM;
 }
 public boolean isGraphicsCardEnabled() {
  return isGraphicsCardEnabled;
 }

 public boolean isBluetoothEnabled() {
  return isBluetoothEnabled;
 }
 private Computer(ComputerBuilder builder) {
  this.HDD=builder.HDD;
  this.RAM=builder.RAM;
  this.isGraphicsCardEnabled=builder.isGraphicsCardEnabled;
  this.isBluetoothEnabled=builder.isBluetoothEnabled;
 }
 //Builder Class
 public static class ComputerBuilder{
  // required parameters
  private String HDD;
  private String RAM;
  // optional parameters
  private boolean isGraphicsCardEnabled;
  private boolean isBluetoothEnabled;
  public ComputerBuilder(String hdd, String ram){
   this.HDD=hdd;
   this.RAM=ram;
  }
  public ComputerBuilder setGraphicsCardEnabled(boolean isGraphicsCardEnabled) {
   this.isGraphicsCardEnabled = isGraphicsCardEnabled;
   return this;
  }
  public ComputerBuilder setBluetoothEnabled(boolean isBluetoothEnabled) {
   this.isBluetoothEnabled = isBluetoothEnabled;
   return this;
  }
  public Computer build(){
   return new Computer(this);
  }
 }
}

There are no public constructors and only getter methods in the computer class. As a result, the only method to get a computer object is to use one.

Here is the builder pattern example.

package com.aglowid.design.test;
import com.aglowid.design.builder.Computer;
public class TestBuilderPattern {
 public static void main(String[] args) {
  //Using builder to get the object in a single line of code and 
                //without any inconsistent state or arguments management issues  
  Computer comp = new Computer.ComputerBuilder(
    "500 GB", "2 GB").setBluetoothEnabled(true)
    .setGraphicsCardEnabled(true).build();
 }
}

Builder Design Pattern Real Example in JDK

Some of the builder pattern example in Java classes are:

  • Java.lang.StringBuilder#append() (unsynchronized)
  • Java.lang.StringBuffer#append() (synchronized)

Abstract Factory

The factory of factories is comparable to the Abstract Factory Design Pattern. If you’re acquainted with the factory design pattern in Java, you’ll see that we have a single factory class that returns different sub-classes depending on the input, and the factory class does so by using if-else or switch statements.

We get rid of the if-else block and have a factory class for each subclass in the abstract factory method pattern, and then an abstract factory class returns the subclass depending on the input factory class.

If you’re familiar with Java’s factory design patterns, you’ll see that the factory class returns multiple subclasses depending on the input. It accomplishes this using if-else or switch statements.

The if-else block is removed in the abstract factory design, and each sub-class has its factory class. The subclass will then be returned by an abstract factory class based on the input factory class. It may appear unclear at first, but as you examine the implementation, the slight difference between Factory and Abstract Factory Patterns becomes clear.

For this example, we will be using the same superclass and subclass as the factory pattern.

Also Read: – Top Java Development Tools to utilize

Abstract Factory Design patterns in Java

Computer.Java

package com.aglowid.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.aglowid.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.aglowid.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;
    }
 
}

Now let’s create an abstract Factory Interface or abstract class.

ComputerAbstractFactory.Java

package com.aglowid.design.abstract factory;

import com.aglowid.design.model.Computer;

public interface ComputerAbstractFactory {

 public Computer createComputer();

}

Notice that the createcomputer() method is returning an instance of a superclass computer. Now factory classes will implement this interface and return their respective sub-class.

PCFactory.Java

package com.aglowid.design.abstractfactory;

import com.aglowid.design.model.Computer;
import com.aglowid.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.aglowid.design.abstract factory;
import com.aglowid.design.model.Computer;
import com.aglowid.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);
 }

}

Similarly, we will create ComputerFactory.Java

ComputerFactory.Java

package com.aglowid.design.abstractfactory;
import com.aglowid.design.model.Computer;
public class ComputerFactory {
 public static Computer getComputer(ComputerAbstractFactory factory){
  return factory.createComputer();
 }
}

Notice that it’s a simple class and the getComputer() method is accepting the ComputerAbstractFactory argument and returns the computer object. At this point, the implementation must be becoming clear.

Let’s write a simple test method and understand how to use the abstract factory to get the instance of the subclass.

package com.aglowid.design.test;

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

public class TestDesignPatterns {

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

 private static void testAbstractFactory() {
  Computer pc = com.aglowid.design.abstractfactory.ComputerFactory.getComputer(new PCFactory("2 GB","500 GB","2.4 GHz"));
  Computer server = com.aglowid.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 is as follows:

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

Let’s move on to the class diagram of abstract factory design pattern implementation.

In the diagram:

  • ComputerFactory is a class responsible for creating instances of computers. It has a static method getComputer() that accepts a ComputerAbstractFactory as a parameter and returns a Computer object.
  • ComputerAbstractFactory is an interface that declares the createComputer() method. Concrete factory classes implement this interface to create specific types of computers.
  • PCFactory and ServerFactory are concrete factory classes that implement the ComputerAbstractFactory interface. They have private fields for ram, hdd, and cpu, and they provide an implementation for the createComputer() method, returning instances of PC and Server classes, respectively.
  • AbstractFactory is an abstract class or interface (not provided in the code snippet) that represents the abstract factory in the design pattern. It may contain common methods or fields shared by concrete factory classes.
  • PC and Server are concrete product classes that extend the Computer abstract class. They have private fields for ram, hdd, and cpu, and they provide implementations for the getRAM(), getHDD(), and getCPU() methods.

The ComputerFactory class acts as a client to the abstract factory and creates instances of Computer objects using the appropriate concrete factory classes (PCFactory and ServerFactory).

Java Abstract Factory Design Pattern Benefits

  • An abstract factory pattern in Java provides an approach to code for interface rather than implementation.
  • Abstract Factory method is easily expandable.
  • Abstract Factory pattern in Java is robust and avoids conditional logic of factory pattern.

Abstract Factory Design Pattern Real-world Examples in JDK

  • Javax.xml.parsers.DocumentBuilderFactory#newInstance()
  • Javax.xml.transform.TransformerFactory#newInstance()
  • Javax.xml.xpath.XPathFactory#newInstance()

Also Read: – Top Java Framworks to use

Use Case of Creational Design Patterns Java

Let’s take a hypothetical scenario of a car rental service company. With this software development scenario, you can easily understand the creational design patterns with practical examples.

Singleton

The car rental company may have a centralized booking system, implemented as a Singleton, to ensure that only one instance of the booking system exists throughout the application. This ensures that all the rental requests and booking management operations are handled consistently.

Factory Method

The rental system may include a VehicleFactory with a factory method that creates different types of vehicles (cars, motorcycles, or vans) based on the customer’s preference or availability. The factory method allows for the flexible creation of different vehicle types while adhering to a common Vehicle interface.

Abstract Factory

To handle different vehicle categories, such as economy, standard, or luxury, the rental system can utilize an Abstract Factory pattern. The abstract factory defines the interface for creating different vehicle categories, and concrete factory implementations produce instances of vehicles that belong to specific categories. This allows the application to generate rental options based on customer preferences or requirements.

Builder

When customers request a rental, a RentalBuilder can be used to construct a rental object. The builder allows step-by-step construction of the rental by setting various parameters such as rental duration, vehicle type, customer details, and additional options. It provides a flexible and customizable way to create rental instances with different configurations.

Prototype

The rental system may offer pre-configured rental packages or templates. These templates can serve as prototypes, allowing customers to select and clone an existing rental package. The prototype pattern enables the creation of new rentals by duplicating the existing templates while customizing certain parameters like rental duration, vehicle type, and optional extras.

By combining these creational design patterns, the car rental application can achieve modularity, flexibility, and maintainability in object creation, ensuring a robust and efficient rental system for the company and its customers.

Wrapping Up!

In Java, creational design patterns are essential for efficient and well-managed object creation. They promote code reusability, modularity, and flexibility while addressing different scenarios for object creation. By utilizing these patterns, developers can design systems that are easier to maintain, extend, and test. A good understanding and application of the appropriate creational design patterns in Java can significantly enhance overall software architecture and contribute to the development of robust and scalable applications.

have a unique app Idea?

Hire Certified Developers To Build Robust Feature, Rich App And Websites

This post was last modified on December 11, 2023 5:08 pm

Saurabh Barot: Saurabh Barot, CTO at Aglowid IT Solutions, brings over a decade of expertise in web, mobile, data engineering, Salesforce, and cloud computing. Known for his strategic leadership, he drives technology initiatives, oversees data infrastructure, and leads cross-functional teams. His expertise spans across Big Data, ETL processes, CRM systems, and cloud infrastructure, ensuring alignment with business goals and keeping the company at the forefront of innovation.
Related Post