DEV Community

Cover image for Design pattern (part 2): Creational patterns
Nguyễn Huy Hoàng
Nguyễn Huy Hoàng

Posted on • Updated on

Design pattern (part 2): Creational patterns

I. What is a creational pattern again?

Creational patterns are design patterns that deal with object creation mechanisms. The main idea of these patterns is to control the object-creation process in a way that it is more flexible and efficient.

The reason behind the pattern is that basic forms of object creation could result in design problems or add complexity to the design. Creational design patterns solve this problem by controlling this object creation process.

II. What are the most common creational patterns?

1. Singleton

- Intent

Think of a printer.

If multiple printers are used simultaneously, managing them can be a nightmare. You can't be sure that the right printer is used for the right job.

Moreover, if you want to change the configuration of the printer, you have to change it for all printers.

- Motivation

The singleton pattern is a creational design pattern that ensures that only one object of its kind exists and provides a single point of access to it for any other code.

In our metaphor, it ensures that only one printer is used for all printing activities. You always know what is the configuration of the printer, so no more confusion.

- Structure

The main idea behind the singleton pattern has two parts:

  • Hide the constructor of the class so that no other code can instantiate it.

  • Use that constructor to instantiate a single instance of the class, and provide a global point of access to it.

- Example

public class Printer {
    private static Printer instance = null;
    private Printer() {}
    public static Printer getInstance() {
        // If no instance exists, create one
        if (instance == null) {
            instance = new Printer();
        }
        // if an instance already exists, return it
        return instance;
    }
}
Enter fullscreen mode Exit fullscreen mode

2. Factory

- Intent

Imagine you work at a transportation company.

Say you have to deliver a top-secret package to a client.

You have to make sure that the package is delivered safely, and that the client is the only one who can open it. Even you, who is delivering the package, are not allowed to know what is inside.

- Motivation

The main idea behind the factory pattern is to create objects without exposing the instantiation logic to the client and refer to newly created objects using a common interface.

In our metaphor, we defer the creation of the package to a factory, and we only know that the general form of the package is a box, so we can't know what is inside.

Not only does this pattern allow us to have zero knowledge of the package, but it also allows us to deliver other types of packages, as long as they are wrapped in a box.

- Structure

The main idea behind the factory pattern has two parts:

  • Create an interface for creating an object, but let subclasses decide which class to instantiate.

  • The caller of the factory method only refers to the factory through the interface, so it doesn't know which class is instantiated.

- Example

public interface Box {
    public void open();
    public void transport();
}
public class SecretBox implements Box {
    public void open() {
        System.out.println("The box is opened");
    }
    public void transport() {
        System.out.println("The box is transported");
    }
}
public interface BoxFactory {
    public Box createBox();
}
public class SecretBoxFactory implements BoxFactory {
    public Box createBox() {
        return new SecretBox();
    }
}
public class Client {
    public void transport(BoxFactory factory) {
        Box box = factory.createBox();
        box.transport();
    }
}
public class Main {
    public static void main(String[] args) {
        Client client = new Client();
        client.transport(new SecretBoxFactory());
    }
}
Enter fullscreen mode Exit fullscreen mode

3. Abstract Factory

- Intent

Imagine you work at a furniture company. (I know, I know, you're tired of the metaphor)

You learned about factories, and you created factories that create tables and chairs and deliver them to clients.

Then, a day later, a client called to complain that the tables and chairs he bought from you don't match.

You realize that you have used factories of different styles to create the tables and chairs, and that's why they don't match.

So what now?

- Motivation

The main idea behind the abstract factory pattern is pretty much the same as the factory pattern, but it extends to creating related objects. Essentially, it is a combination of the factories of multiple objects into a single factory.

In our metaphor, we have a factory interface that creates tables and chairs, and we want to create a factory that creates tables and chairs of different styles. When we call the factory, we can always be certain that the table and the chair are of the same style, without having to know which style it is.

- Structure

The main idea behind the abstract factory pattern has three parts:

  • Each concrete class of a family has to implement a family of interfaces.

  • Create a factory interface for creating that family of interfaces without specifying their concrete classes and defer the creation of the concrete class to subclasses.

  • The caller of the abstract factory method only refer to the factory through the interface, so it doesn't know which style is instantiated. However, it knows that the table and the chair are of the same style.

- Example

public interface Table {
    public void create();
}
public interface Chair {
    public void create();
}
public class ModernTable implements Table {
    public void create() {
        System.out.println("A modern table is created");
    }
}
public class ModernChair implements Chair {
    public void create() {
        System.out.println("A modern chair is created");
    }
}
public class VictorianTable implements Table {
    public void create() {
        System.out.println("A victorian table is created");
    }
}
public class VictorianChair implements Chair {
    public void create() {
        System.out.println("A victorian chair is created");
    }
}
public interface FurnitureFactory {
    public Table createTable();
    public Chair createChair();
}
public class ModernFurnitureFactory implements FurnitureFactory {
    public Table createTable() {
        return new ModernTable();
    }
    public Chair createChair() {
        return new ModernChair();
    }
}
public class VictorianFurnitureFactory implements FurnitureFactory {
    public Table createTable() {
        return new VictorianTable();
    }
    public Chair createChair() {
        return new VictorianChair();
    }
}
public class Client {
    public void createFurniture(FurnitureFactory factory) {
        Table table = factory.createTable();
        Chair chair = factory.createChair();
        table.create();
        chair.create();
    }
}
public class Main {
    public static void main(String[] args) {
        Client client = new Client();
        client.createFurniture(new ModernFurnitureFactory());
        client.createFurniture(new VictorianFurnitureFactory());
    }
}
Enter fullscreen mode Exit fullscreen mode

4. Builder

- Intent

Again, imagine you work at a construction company. (Oh well, here we go again)

For a house, you know that it has to be built in a certain order: the foundation, the walls, the roof, and so on.

However, for each step, you can use different materials, different styles, and so on. You don't know what the client wants, so you have to give the client the freedom to choose what materials and styles to use.

- Motivation

The main idea behind the builder pattern is to delegate the construction of a complex object to other objects called builders.

In our metaphor, we have a construction company that builds houses. The company has a builder interface that defines the steps of building a house.

The company has a director that uses the builders that implement the interface to build a house. The director doesn't know the details of how builders build the house, but he knows that the house is built in a certain order.

Note that in the pattern, the client can use the director to build a house, and the client can also use the builder interface directly to build a house itself.

- Structure

The main idea behind the builder pattern has three parts:

  • Create a builder interface that defines the steps of building a complex object.

  • For each step, create a concrete builder that implements the builder interface and builds the step.

  • Create a director that uses the builder interface to build a complex object. The director doesn't know the concrete classes of the builders, but he knows the order of the steps.

- Example

public interface HouseBuilder {
    public void buildFoundation();
    public void buildWalls();
    public void buildRoof();
    public House getHouse();
}
public class ModernHouseBuilder implements HouseBuilder {
    private House house;
    public ModernHouseBuilder() {
        house = new House();
    }
    public void buildFoundation() {
        house.setFoundation("Modern foundation");
    }
    public void buildWalls() {
        house.setWalls("Modern walls");
    }
    public void buildRoof() {
        house.setRoof("Modern roof");
    }
    public House getHouse() {
        return house;
    }
}
public class VictorianHouseBuilder implements HouseBuilder {
    private House house;
    public VictorianHouseBuilder() {
        house = new House();
    }
    public void buildFoundation() {
        house.setFoundation("Victorian foundation");
    }
    public void buildWalls() {
        house.setWalls("Victorian walls");
    }
    public void buildRoof() {
        house.setRoof("Victorian roof");
    }
    public House getHouse() {
        return house;
    }
}
public class House {
    private String foundation;
    private String walls;
    private String roof;
    public void setFoundation(String foundation) {
        this.foundation = foundation;
    }
    public void setWalls(String walls) {
        this.walls = walls;
    }
    public void setRoof(String roof) {
        this.roof = roof;
    }
    public String getFoundation() {
        return foundation;
    }
    public String getWalls() {
        return walls;
    }
    public String getRoof() {
        return roof;
    }
}
public class Director {
    public void buildHouse(HouseBuilder builder) {
        builder.buildFoundation();
        builder.buildWalls();
        builder.buildRoof();
    }
}
public class Client {
    public void buildHouse(HouseBuilder builder) {
        Director director = new Director();
        director.buildHouse(builder);
        House house = builder.getHouse();
        System.out.println(house.getFoundation());
        System.out.println(house.getWalls());
        System.out.println(house.getRoof());
    }
}
public class Main {
    public static void main(String[] args) {
        Client client = new Client();
        client.buildHouse(new ModernHouseBuilder());
        client.buildHouse(new VictorianHouseBuilder());
    }
}
Enter fullscreen mode Exit fullscreen mode

5. Prototype

- Intent

Imagine you are working on a Microsoft Docs file (Better now). You have a document with a lot of text, and you want to create a copy of the document.

You can copy the text, but then you will lose other information such as the formatting of the text, the metadata, and so on.

You can go through the document and copy each piece of information, but that will take a lot of time. Also, sometimes you only know the general structure of the document, but you don't know the details of the document.

- Motivation

The prototype pattern is used when you want to create a copy of an object, but you don't know the details of the object.

It delegates the creation of the copy to the object itself. The object that you want to copy is called the prototype.

Thus, you can copy all the information of the object, including the internal, private information. Moreover, by creating a common interface for all the objects, you can copy objects without knowing their concrete classes.

- Structure

The main idea behind the prototype pattern has three parts:

  • Create a prototype interface that defines the clone method.

  • Create concrete prototypes that implement the clone method. The clone method creates a copy of the object.

  • Create a client that uses the clone method to create a copy of an object.

- Example

public interface Document extends Cloneable {
    public Document clone();
}
public class WordDocument implements Document {
    private String text;
    public WordDocument(String text) {
        this.text = text;
    }
    public Document clone() {
        return new WordDocument(text);
    }
    public String getText() {
        return text;
    }
}
public class Client {
    public void createDocument(Document document) {
        Document documentCopy = document.clone();
        System.out.println(documentCopy.getText());
    }
}
public class Main {
    public static void main(String[] args) {
        Client client = new Client();
        client.createDocument(new WordDocument("Hello world"));
    }
}
Enter fullscreen mode Exit fullscreen mode

III. Summary

In this article, we learned about the creational design pattern type, and we learned about the main patterns of the type:

  • Singleton

  • Factory

  • Abstract factory

  • Builder

  • Prototype

That's all for this week's article. I hope you enjoyed it. If you have any questions, feel free to ask them in the comments.

Top comments (0)