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 ofDatabaseConnection
. 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 ofDatabaseConnection
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 theLogger
interface and provides an implementation for writing log messages to a file. - The
DatabaseLogger
class also implements theLogger
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 agetLogger()
method that takes atype
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 forusername
,password
, andemail
, and a private constructor that takes aUserBuilder
object to set these fields. - The
UserBuilder
is a nested static class within theUser
class, responsible for constructing theUser
object. - The
UserBuilder
class provides a public constructor that takes required parameters (username
andpassword
). - It also provides a
setEmail()
method to set the optionalemail
field and returns the builder instance for method chaining. - The
build()
method is used to create a newUser
object by calling the privateUser
constructor with theUserBuilder
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 atype
field and an abstractdraw()
method that needs to be implemented by its subclasses. - The
clone()
method is overridden from theCloneable
interface. It creates and returns a copy of the object by calling theclone()
method of the superclass. - The
Circle
andRectangle
classes extend theShape
class and provide their own implementations for thedraw()
method. They also set thetype
field to specify the type of shape they represent. - To create a new
Circle
object, theclone()
method is used on an existingCircle
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.