NashTech Insights

Overview of Creational Patterns

anhtrannamtuan
anhtrannamtuan
Table of Contents
photo of people doing handshakes

Introduction

Creational Patterns are a vital group of Design Patterns that focus on the object creation process in software development. In this blog post, we will explore Creational Patterns in Java, provide illustrative examples, discuss their advantages and disadvantages, and explore scenarios where they are most useful.

What are Creational Patterns

Creational Patterns are a set of design patterns in software development that concentrate on flexible and reusable object creation. They provide standardized approaches for object creation and abstract the creation logic from the client.

Types of Creational Patterns

Singleton Pattern

Description: Ensures that only one instance of a class is created and provides a global point of access to it.

Example: The DatabaseConnection class in a database management system needs only one connection instance for accessing data

public class DatabaseConnection {
private static DatabaseConnection instance;

private DatabaseConnection() {
// Initialize the connection to the database
}

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

// Other methods to perform data querying operations
}

We have a class name DatabaseConnection that implements the Singleton pattern.

  • The class has a private constructor, which ensures that no other class can directly instantiate it.
  • The getInstance() method is a static method that provides the only way to access the instance of DatabaseConnection. It follows the Singleton pattern by returning the same instance every time it is called.
  • The instance variable is declared as private and static, allowing only one instance of DatabaseConnection to exist throughout the application.
  • The constructor is responsible for initializing the connection to the database. Since it is private, only the getInstance() method can call it.

By using the Singleton pattern, we can ensure that there is only one instance of the DatabaseConnection class, which allows for efficient resource utilization and avoids unnecessary multiple connections to the database. This pattern is commonly used in scenarios where a single instance of a class is required to coordinate actions across the system, such as managing shared resources or accessing a global configuration.

Factory Pattern

Description: Creates objects without exposing the object creation logic to the client.

Example: The LoggerFactory class creates Logger objects based on the client’s requirements.We have three classes that demonstrate the Factory pattern for creating different types of loggers.

public interface Logger {
    void log(String message);
}

public class FileLogger implements Logger {
    @Override
    public void log(String message) {
        // Write log to file
    }
}

public class DatabaseLogger implements Logger {
    @Override
    public void log(String message) {
        // Store log in the database
    }
}

public class LoggerFactory {
    public static Logger getLogger(String type) {
        if (type.equalsIgnoreCase("file")) {
            return new FileLogger();
        } else if (type.equalsIgnoreCase("database")) {
            return new DatabaseLogger();
        }
        return null;
    }
}

The Logger interface defines a common contract for logging operations, specifying a log() method that takes a message as input.

  • The FileLogger class implements the Logger interface and provides an implementation for writing log messages to a file.
  • The DatabaseLogger class also implements the Logger interface and provides an implementation for storing log messages in a database.
  • The LoggerFactory class serves as a factory that creates instances of different types of loggers based on the given type parameter. It provides a getLogger() method that takes a type argument (either “file” or “database”) and returns the corresponding logger instance.

By using the Factory pattern, we can encapsulate the object creation logic and provide a centralized way to create different types of loggers. This allows for flexibility and extensibility in adding new types of loggers in the future without modifying the client code. The Factory pattern is commonly used in scenarios where there is a need for dynamic object creation or when the instantiation process is complex.

Builder Pattern

Description: Constructs complex objects step by step, allowing us to create multiple variations and representations of an object.

Example: The StringBuilder class in Java facilitates easy construction of strings by concatenating elements.

public class User {
    private String username;
    private String password;
    private String email;
    
    private User(UserBuilder builder) {
        this.username = builder.username;
        this.password = builder.password;
        this.email = builder.email;
    }
    
    // Getters
    
    public static class UserBuilder {
        private String username;
        private String password;
        private String email;
        
        public UserBuilder(String username, String password) {
            this.username = username;
            this.password = password;
        }
        
        public UserBuilder setEmail(String email) {
            this.email = email;
            return this;
        }
        
        public User build() {
            return new User(this);
        }
    }
}

// Using the Builder Pattern to create a User object
User user = new User.UserBuilder("johnsmith", "password")
                .setEmail("john@example.com")
                .build();

We have a User class that utilizes the Builder pattern for constructing user objects with optional parameters.

  • The User class has private fields for username, password, and email, and a private constructor that takes a UserBuilder object to set these fields.
  • The UserBuilder is a nested static class within the User class, responsible for constructing the User object.
  • The UserBuilder class provides a public constructor that takes required parameters (username and password).
  • It also provides a setEmail() method to set the optional email field and returns the builder instance for method chaining.
  • The build() method is used to create a new User object by calling the private User constructor with the UserBuilder instance.

By using the Builder pattern, we can simplify the object creation process, especially when dealing with objects with multiple optional parameters. It improves readability and eliminates the need for multiple constructors or setter methods. The Builder pattern is particularly useful when there is a need for immutability, easy extensibility, and flexible object construction.

Prototype Pattern

Description: Copies existing objects to create new objects instead of creating them from scratch.

Example: Using the clone() method in Java to clone an existing object.

public abstract class Shape implements Cloneable {
    protected String type;
    
    public abstract void draw();
    
    @Override
    public Object clone() {
        Object clone = null;
        try {
            clone = super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return clone;
    }
}

public class Circle extends Shape {
    public Circle() {
        this.type = "Circle";
    }
    
    @Override
    public void draw() {
        System.out.println("Drawing a circle");
    }
}

public class Rectangle extends Shape {
    public Rectangle() {
        this.type = "Rectangle";
    }
    
    @Override
    public void draw() {
        System.out.println("Drawing a rectangle");
    }
}

// Creating a Circle object by cloning an existing object
Circle circle = (Circle) new Circle().clone();

We have an abstract class Shape that implements the Prototype pattern through the Cloneable interface.

  • The Shape class is an abstract class with a type field and an abstract draw() method that needs to be implemented by its subclasses.
  • The clone() method is overridden from the Cloneable interface. It creates and returns a copy of the object by calling the clone() method of the superclass.
  • The Circle and Rectangle classes extend the Shape class and provide their own implementations for the draw() method. They also set the type field to specify the type of shape they represent.
  • To create a new Circle object, the clone() method is used on an existing Circle object. This performs a shallow copy of the object, creating a new instance with the same field values.

By using the Prototype pattern, we can create new objects by cloning existing objects, avoiding the need for explicit construction and configuration. This can be useful when creating multiple similar objects with slight variations or when object creation is costly in terms of time or resources. The Prototype pattern allows for object creation without relying on subclasses or directly invoking constructors.

Advantages and Disadvantages of Creational Patterns

Advantages

  • Provides flexible and reusable object creation.
  • Hides the creation logic from the client, reducing dependencies and increasing flexibility.
  • Offers a standardized and understandable approach to object creation.

Disadvantages

  • Increases source code complexity and may pose challenges in understanding and maintenance.
  • Some patterns can result in a proliferation of related classes and objects, adding to management overhead.

When to Use Creational Patterns

  • When there is a need for flexible object creation and decoupling from specific classes.
  • When the creation logic needs to be hidden from the client, providing a standardized access point.
  • When there is a requirement to create multiple variations and representations of an object.

Conclusion

Creational Patterns provide standardized solutions for the object creation process in Java software development. They enhance flexibility, reusability, and reduce dependencies. Understanding and correctly applying Creational Patterns will empower us to build flexible and maintainable software systems.

anhtrannamtuan

anhtrannamtuan

Anh Tran is Technical Lead at NashTech. With several years of experience in software development, I have had the opportunity to participate in and contribute to complex technology projects. With a cheerful attitude and a passion for learning, I always seek opportunities to explore new things in the technology industry. I believe that staying updated and applying emerging trends will bring maximum value and effectiveness to projects.

Leave a Comment

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

Suggested Article

%d bloggers like this: