Creational design patterns are focused on the process of object creation, ensuring that the system remains flexible and reusable. These patterns abstract the instantiation process, allowing the creation logic to be separated from the usage of objects. Here’s a detailed guide with step-by-step examples in Java and applications in Spring Framework.
Key Creational Design Patterns with Examples
1. Singleton Pattern
Ensures a class has only one instance and provides a global point of access to it.
Steps to Implement
- Make the constructor private to prevent direct instantiation.
- Create a static variable to hold the single instance.
- Provide a static method to return the instance.
Java Example
public class Singleton {
private static Singleton instance;
private Singleton() {
// private constructor
}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
Spring Framework Example
- Spring Beans: Beans defined with
@Scope("singleton")are Singleton by default.
@Component
public class SingletonBean {
public void display() {
System.out.println("Singleton instance");
}
}
Real-World Use Case
- Database Connection Manager: Ensures only one connection instance exists in the application.
Advantages
- Saves memory by avoiding multiple instances.
- Centralized control over the instance.
Disadvantages
- Hard to test due to global access.
- May introduce tight coupling.
2. Factory Method Pattern
Defines an interface for creating objects but lets subclasses decide which class to instantiate.
Steps to Implement
- Create an interface or abstract class for the product.
- Create concrete implementations of the product.
- Create a factory interface or abstract class with a factory method.
- Implement concrete factories for different products.
Java Example
interface Shape {
void draw();
}
class Circle implements Shape {
public void draw() {
System.out.println("Drawing a Circle");
}
}
class Rectangle implements Shape {
public void draw() {
System.out.println("Drawing a Rectangle");
}
}
class ShapeFactory {
public Shape getShape(String shapeType) {
if (shapeType.equalsIgnoreCase("CIRCLE")) {
return new Circle();
} else if (shapeType.equalsIgnoreCase("RECTANGLE")) {
return new Rectangle();
}
return null;
}
}
Spring Framework Example
- Bean Factory: The
FactoryBeaninterface in Spring allows creating objects on demand.
@Component
public class ShapeFactory implements FactoryBean<Shape> {
private String shapeType;
public void setShapeType(String shapeType) {
this.shapeType = shapeType;
}
@Override
public Shape getObject() throws Exception {
if (shapeType.equalsIgnoreCase("CIRCLE")) {
return new Circle();
} else if (shapeType.equalsIgnoreCase("RECTANGLE")) {
return new Rectangle();
}
return null;
}
@Override
public Class<?> getObjectType() {
return Shape.class;
}
}
Real-World Use Case
- Document Parsers: Creating parsers for XML, JSON, etc.
Advantages
- Promotes loose coupling.
- Adds flexibility in the type of objects created.
Disadvantages
- Requires additional code for factories.
- May increase the complexity of class structures.
3. Abstract Factory Pattern
Provides an interface to create families of related or dependent objects without specifying their concrete classes.
Steps to Implement
- Create abstract product interfaces.
- Create concrete product implementations.
- Create an abstract factory interface with methods to produce abstract products.
- Implement concrete factories for each product family.
Java Example
interface Button {
void render();
}
class WindowsButton implements Button {
public void render() {
System.out.println("Rendering Windows Button");
}
}
class MacOSButton implements Button {
public void render() {
System.out.println("Rendering MacOS Button");
}
}
interface GUIFactory {
Button createButton();
}
class WindowsFactory implements GUIFactory {
public Button createButton() {
return new WindowsButton();
}
}
class MacOSFactory implements GUIFactory {
public Button createButton() {
return new MacOSButton();
}
}
Spring Framework Example
- ApplicationContext: In Spring, the
ApplicationContextacts as a factory to manage object creation for multiple related objects.
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class); Button button = context.getBean(Button.class); button.render();
Real-World Use Case
- Cross-Platform GUIs: Ensures consistency across platforms.
Advantages
- Ensures consistency among related objects.
- Hides concrete classes from the client.
Disadvantages
- Adding new families requires changes to all factories.
4. Builder Pattern
Separates the construction of a complex object from its representation.
The Builder Pattern is a creational design pattern that helps you construct complex objects step by step, without having to pass a huge list of parameters or create tons of constructors.
🔧 Why Use the Builder Pattern?
- To avoid constructor overloading hell (when you have too many constructor variations).
- When you want to create immutable objects but still need flexibility while setting fields.
- When the object has many optional parameters.
🧱 Key Idea:
“Separate the construction of a complex object from its representation, so that the same construction process can create different representations.”
This means:
- You use a Builder to build the object.
- The object itself (the Product) is kept separate from how it’s built.
Steps to Implement
- Create a static nested builder class.
- Add methods to the builder for setting properties.
- Add a build method to return the constructed object.
- Make the constructor of the main class private.
Java Example
class House {
private String foundation;
private String walls;
private String roof;
private House(Builder builder) {
this.foundation = builder.foundation;
this.walls = builder.walls;
this.roof = builder.roof;
}
public static class Builder {
private String foundation;
private String walls;
private String roof;
public Builder foundation(String foundation) {
this.foundation = foundation;
return this;
}
public Builder walls(String walls) {
this.walls = walls;
return this;
}
public Builder roof(String roof) {
this.roof = roof;
return this;
}
public House build() {
return new House(this);
}
}
}
Spring Framework Example
- Spring WebClient: The WebClient builder in Spring follows the Builder Pattern.
WebClient client = WebClient.builder()
.baseUrl("http://example.com")
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.build();
Real-World Use Case
- Complex Configuration: Building objects like HTTP clients or query builders.
Advantages
- Simplifies object creation.
- Provides a clear and readable object construction process.
Disadvantages
- Adds complexity with additional builder classes.
5. Prototype Pattern
Creates objects by cloning an existing instance.
“Creates objects by cloning an existing instance, rather than creating from scratch.”
This pattern is helpful when:
- Object creation is costly (e.g., performance, memory).
- You want to create a copy of an object without knowing its exact class.
- You want to avoid using the
newkeyword repeatedly.
🔄 Core Idea
- You have a prototype object (an original).
- Instead of creating a new one from scratch, you clone the prototype.
- Cloning can be shallow or deep, depending on the use case.
Steps to Implement
- Create a prototype interface with a clone method.
- Implement concrete classes that override the clone method.
Java Example
public class Person implements Cloneable {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
// Implement clone() method
@Override
public Person clone() {
try {
return (Person) super.clone(); // Shallow copy
} catch (CloneNotSupportedException e) {
throw new AssertionError();
}
}
@Override
public String toString() {
return name + ", age " + age;
}
}
main(){
Person original = new Person("Alice", 30);
Person copy = original.clone();
System.out.println(original); // Alice, age 30
System.out.println(copy); // Alice, age 30
}
Spring Framework Example
- Prototype Scope: Spring beans with
@Scope("prototype")use the Prototype Pattern.
@Component
@Scope("prototype")
public class PrototypeBean {
public void show() {
System.out.println("Prototype Bean Instance");
}
}
Real-World Use Case
- Game Development: Cloning enemies or objects with the same state.
Advantages
- Reduces object creation overhead.
- Works well for expensive-to-create objects.
Disadvantages
- Cloning complex objects can be error-prone.
6. Object Pool Pattern
Reuses objects that are expensive to create by maintaining a pool of objects for reuse.
Steps to Implement
- Create a pool manager class that holds a collection of reusable objects.
- Provide methods to borrow and return objects to the pool.
- Ensure thread safety when accessing the pool.
Java Example
class ObjectPool {
private List<Connection> availableConnections = new ArrayList<>();
public ObjectPool(int size) {
for (int i = 0; i < size; i++) {
availableConnections.add(new Connection());
}
}
public synchronized Connection borrowObject() {
if (availableConnections.isEmpty()) {
return new Connection(); // Create new if pool is empty
}
return availableConnections.remove(0);
}
public synchronized void returnObject(Connection connection) {
availableConnections.add(connection);
}
}
class Connection {
public void connect() {
System.out.println("Connection established");
}
}
Real-World Use Case
- Database Connection Pools: JDBC connection pooling.
Advantages
- Improves performance by reusing costly objects.
Disadvantages
- Requires careful management to avoid resource leaks.
7. Multiton Pattern
Similar to Singleton but allows a fixed number of instances.
“Similar to the Singleton Pattern, but instead of just one instance, it allows a fixed set of instances, each identified by a unique key.”
🔑 Key Concepts:
- Singleton: Only one instance for the entire application.
- Multiton: A controlled map of instances, each associated with a key (e.g., name, type, ID).
- Ensures that for a given key, only one instance is created and reused.
📦 Use Case Example:
- You want only one
DatabaseConnectionper region:"US","EU","ASIA". - You don’t want to create a new instance each time; reuse existing ones per region.
Steps to Implement
- Maintain a static map to hold instances.
- Restrict instance creation to a predefined set of keys.
- Provide a method to retrieve instances based on keys.
Java Example
import java.util.HashMap;
import java.util.Map;
public class DatabaseConnection {
private static final Map<String, DatabaseConnection> instances = new HashMap<>();
private final String region;
// Private constructor
private DatabaseConnection(String region) {
this.region = region;
}
// Static factory method
public static synchronized DatabaseConnection getInstance(String region) {
if (!instances.containsKey(region)) {
instances.put(region, new DatabaseConnection(region));
}
return instances.get(region);
}
public String getRegion() {
return region;
}
}
Real-World Use Case
- Configuration Managers: Managing configurations for different environments (e.g., dev, test, prod).
Advantages
- Provides controlled multiple instances.
Disadvantages
- Can become complex to manage keys and states.
8. Static Factory Method
A static method to encapsulate object creation logic, often used as a simpler alternative to constructors.
Steps to Implement
- Create a class with a static method for object creation.
- Encapsulate any additional creation logic in the static method.
Java Example
class Product {
private String name;
private Product(String name) {
this.name = name;
}
public static Product createProduct(String name) {
// Additional logic can be added here
return new Product(name);
}
}
Real-World Use Case
- Factory Methods in Java Libraries:
java.util.Collections.singletonList().
Advantages
- Simplifies object creation.
- Provides a more descriptive method name than constructors.
Disadvantages
- Does not support inheritance.
This document now includes all 8 creational design patterns with detailed descriptions, examples, and applications in real-world and Spring Framework contexts.