Top Creational Design Patterns With Real Examples in Java

Amirhosein Gharaati
Javarevisited
Published in
9 min readSep 18, 2023

We can apply solutions to commonly occurring problems by knowing design patterns in software design.

Software creational patterns can help us when we want to create objects in some certain scenarios, which increase flexibility.

Table of Contents

Design patterns

Design patterns are solutions to commonly occurring problems in software design. They are like blueprints that you can customize to solve a recurring design problem in your code.

The pattern is not a specific piece of code, but a general concept that you can apply it in your software or customize them and use your own.

Why should we learn design patterns?

There are couple reasons to learn design patterns:

  1. They are tried and tested solutions: and even if you don’t encounter with those problems, knowing patterns teach you how to solve them in object oriented design.
  2. They define a common language that you and your teammates can use to communicate with each other more efficiently. When you say “Factory”, maybe everyone understands the idea that you are talking about.

What does the pattern consist of?

The sections that are usually present in a pattern description are:

  • Intent: describes both the problem and the solution.
  • Motivation: further explains the problem and the solution the pattern makes possible.
  • Structure: shows each part of the pattern and how they are related.
  • Code example: in a programming language to show the solution.

But for simplicity, we summarize these parts.

Classification of Patterns

There are three main groups of design patterns in software development:

  • Creational patterns: provide object creation mechanisms that increase flexibility and reuse of existing code.
  • Structural patterns: explain how to assemble objects and classes into larger structures, while keeping these structures flexible and efficient.
  • Behavioral patterns: take care of effective communication and the assignment of responsibilities between objects.

In this article we only talk about popular creational patterns.

You can see the implementations in this repo:

https://github.com/AmirHosein-Gharaati/design-patterns

Creational patterns

So in these patterns, the main focus is on creating objects while reusing existing code.

Some popular patterns of creational patterns are:

Singleton

Lets you ensure that a class has only one instance, while providing a global access point to this instance.

One of the best examples of Singleton pattern is for “Database”. In some of our projects, we have only one instance of database connection. You can find this example through other articles or websites, but let’s go for another common example.

Assume you have a configuration class in your system. Rephrasing it: You have one and only one configuration class in your system. So you don’t want to create multiple objects, since the properties are same for you entire system.

Singleton can help us, providing just one instance around your software or system.

Create a config.yaml file in “resources” folder:

property-one: 1
property-two: 1
property-three: 1

And this is a simple Configuration class we can have:

@Getter
public class Configuration {
private static Configuration instance;
private final String CONFIG_FILE_PATH = "/config.yaml";

private String propertyOne;
private String propertyTwo;
private String propertyThree;

public static Configuration getInstance() {
if(instance == null) {
instance = new Configuration();
}
return instance;
}

private Configuration() {
loadConfig();
}

private void loadConfig() {
try (InputStream input =
getClass().getResourceAsStream(CONFIG_FILE_PATH)) {
Yaml yaml = new Yaml();
Map<String, Object> config = yaml.load(input);

if (config != null) {
propertyOne = config.get("property-one").toString();
propertyTwo = config.get("property-two").toString();
propertyThree = config.get("property-three").toString();
}

} catch (IOException e) {
throw new RuntimeException("Error while reading config: %s"
.formatted(e.getMessage()));
}
}
}

Now we can access it through our project:

Main.class

public class Main {
public static void main(String[] args) {
Configuration config = Configuration.getInstance();

System.out.println(config.getPropertyOne());
System.out.println(config.getPropertyTwo());
System.out.println(config.getPropertyThree());
}
}

Applicability

Use singleton pattern when:

  • A class in your program should have just a single instance available to all clients
  • You need stricter control over global variables.

Pros

  • You are sure that a class has only one instance.
  • A global access is available to your instance.
  • It is initialized only once.

Cons

  • The pattern requires special treatment in a multithreaded environment so that multiple threads won’t create a singleton object several times.
  • It may be difficult to unit test the client code of the Singleton.

Note: There are other strategies to make a singleton in different scenarios. Check out this link:

Read more about singleton:

Builder

Lets you construct complex objects step by step. The pattern allows you to produce different types and representations of an object using the same construction code.

There are different examples to show where builder comes for help. But just for a general example:

You want to create an object which has many different fields (some mandatory and some optional).

Creating the object may be complex or messy with traditional ways. Imagine an object has 2 mandatory fields and 8 optional ones. There would be many many different constructors with different parameters!!!

So builder solves the problem. How?

Assuming we have Product class that has different fields. Let’s say productId is mandatory and each other field is optional:

@Getter
public class Product {
private final String productId;
private final String name;
private final String color;
private final int price;
}

Now you create a Builder class within Product:

@Getter
public class Product {
private final String productId;
private final String name;
private final String color;
private final int price;

private Product(Builder builder) {
this.productId = builder.productId;
this.name = builder.name;
this.color = builder.color;
this.price = builder.price;
}

public static Builder builder(String productId) {
return new Builder(productId);
}

public static class Builder {
private final String productId;
private String name;
private String color;
private int price;

private Builder(String productId) {
this.productId = productId;
}

public Builder name(String name) {
this.name = name;
return this;
}

public Builder color(String color) {
this.color = color;
return this;
}

public Builder price(int price) {
this.price = price;
return this;
}

public Product build() {
return new Product(this);
}
}
}

Note
The Builder nested class is static . Why?
Because if it is non-static, it would require an instance of its owning class. The point is not to have an instance of Product, and even to forbid making instances without the builder.

Read more about nested classes: https://docs.oracle.com/javase/tutorial/java/javaOO/nested.html

Now we can easily create a product with any optional parameters we want:

Main

public class Main {
public static void main(String[] args) {
Product product = Product.builder("12345")
.name("Product Name")
.color("Product Color")
.price(100)
.build();

System.out.println("Product ID is: %s".formatted(product.getProductId()));
}
}

Lombok

You can achieve a simple builder pattern for your object using @Builder lombok annotation.
You can read more configurations about lombok @Builder by yourself.

Applicability

Use builder pattern when:

  • You want to get rid of a “telescoping constructor”:
public class Product{
public Product(String productId) {}
public Product(String productId, String name) {...}
public Product(String productId, String name, String color) {...}
public Product(String productId, String name, String color, int price) {...}
...
}
  • You want your code to be able to create different representations of some products.

Pros

  • Single Responsibility Principle. You can isolate complex construction code from the business logic of the product.
  • More readable code for object construction.
  • You can construct objects step-by-step (or defer construction, or run steps recursively).

Cons

  • The overall complexity increases.

Read more about builder pattern:

Factory Method

Provides an interface for creating objects in a superclass, but allows subclasses to alter the type of objects that will be created.

Imagine you have a system that has discounts for some products. You have different types of discounts. In your code you need to create “Discount” objects based on the data.

So one solution is: defining an abstract class named “Discount” and let other concrete classes extend abstract class (You can also have interfaces).

Then, let another class named “DiscountFactory” create different kinds of Discounts based on the data. Now, you only need to call the factory method you defined with your parameters.

Here is the implementation:

Discount abstract class

@Getter
@AllArgsConstructor
public abstract class Discount {
protected String name;
protected int basePrice;
protected int finalPrice;
}

Define two different concrete classes named:
SimpleDiscount

@Getter
public class SimpleDiscount extends Discount {
private final String productId;

public SimpleDiscount(String name, int basePrice, int lastPrice, String productId) {
super(name, basePrice, lastPrice);
this.productId = productId;
}
}

and ComplexDiscount

@Getter
public class ComplexDiscount extends Discount {
private final List<String> productIds;

public ComplexDiscount(String name, int basePrice, int lastPrice, List<String> productIds) {
super(name, basePrice, lastPrice);
this.productIds = productIds;
}
}

Notice for saying which type of discount we want, we create an enum for it: DiscountType. You can come up with your own idea.

public enum DiscountType {
SIMPLE, COMPLEX;
}

Create a class for factory’s data:
FactoryData

@Getter
@AllArgsConstructor
public class FactoryData {
private final DiscountType discountType;

private final String name;
private final int basePrice;
private final int finalPrice;
private final List<String> productIds;
}

Create a factory class with a method named “create” to create a Discount object based on data

DiscountFactory

@NoArgsConstructor
public class DiscountFactory {

public Discount create(FactoryData data) {
if(data.getDiscountType().equals(DiscountType.SIMPLE)) {
return createSimpleDiscount(data);
} else if(data.getDiscountType().equals(DiscountType.COMPLEX)) {
return createComplexDiscount(data);
}

throw new IllegalStateException("Bad discount type: %s".formatted(data.getDiscountType().toString()));
}

private Discount createSimpleDiscount(FactoryData data) {
return new SimpleDiscount(
data.getName(),
data.getBasePrice(),
data.getFinalPrice(),
data.getProductIds().get(0)
);
}

private Discount createComplexDiscount(FactoryData data) {
return new ComplexDiscount(
data.getName(),
data.getBasePrice(),
data.getFinalPrice(),
data.getProductIds()
);
}
}

Now you only need to call create method of factory:
Main

public class Main {
public static void main(String[] args) {
FactoryData factoryData = new FactoryData(
DiscountType.SIMPLE,
"simple discount",
100,
95,
List.of("568")
);

DiscountFactory factory = new DiscountFactory();

Discount discount = factory.create(factoryData);
System.out.println(discount.getName());
}
}

Applicability

Use factory pattern when:

  • You don’t know beforehand the exact types and dependencies of the objects your code should work with.
  • You want to provide users of your library or framework with a way to extend its internal components.
  • You want to save system resources by reusing existing objects instead of rebuilding them each time.

Pros

  • You avoid tight coupling between the creator and the concrete products.
  • Single Responsibility Principle. You move the logic of creating the product into another special class.
  • Open/Closed principle. You can add more types to the factory without breaking anything.

Cons

  • The code may become more complicated since you need to introduce a lot of new subclasses to implement the pattern.

Read more about factory pattern:

More creational patterns

There are other creational patterns which I just give a simple example for each of them. You can read more about them in provided link.

Abstract Factory

Produce families of related objects without specifying their concrete classes.

You are working on a software which you want this to be compatible with different operating systems or in different operating systems, you want to create some special objects which may differ a little bit.

Your objects relate to each other, but for a specific operating system, you are going to create special objects for them. Hiding the object creation, we can have different factories, and based on the operating system we get a specific factory with factory interface.

Prototype

Copy existing objects without making your code dependent on their classes.

Developing a game, there are some enemies in the system which consecutive enemy objects should be created.

You want to clone enemy and create an object based on another. You implement an interface for your object to prototype and clone (or create a new) object based on that.

Object Pool

Optimize resource management by recycling and reusing objects rather than creating new ones.

Building a HTTP server is really complex. Specially when you want to have a good performance in memory allocation.

Handling each request can be resource-intensive, especially if you need to create and destroy request-handling objects frequently. By using an Object Pool, you can efficiently manage and reuse these objects.

More Resources

Conclusion

There are different groups of design patterns in software design which we only talked about “Creational patterns”.

Choosing the proper pattern, can make our project more flexible and reusable. We should only get the idea behind a pattern, and apply it to our software.

In future, we will talk about other groups of patterns.

--

--