Creational Pattern
Description
Creational patterns in software engineering deal with the process of object creation in a flexible and reusable manner. They aim to provide mechanisms to instantiate objects in various situations while promoting loose coupling between the creator and the created objects. Creational patterns often involve hiding the object creation logic and making the system more independent of how its objects are created, composed, and represented
Types of Creational Pattern
Singleton Pattern
Description
The Singleton Pattern ensures a class has only one instance and provides a global point of access to that instance. It involves creating a class with a method that returns the same instance of the class every time it's called, thus restricting the instantiation of the class to a single object. It's like having a single source of truth for a specific concept within your application.
Imagine a scenario where we only need one instance of a class throughout the application. For example, a system might have a single Logger
object to handle all logging activities, or a configuration manager to hold all application settings. The Singleton Pattern ensures this by:
Restricting object creation: The constructor of the Singleton class is typically private or protected, preventing direct instantiation from outside the class.
Providing a static method: A public static method, often named
getInstance()
, is used to access the single instance. If the instance doesn't exist, it's created on the first call and returned. Subsequent calls reuse the same instance.
Benefits of Singleton Pattern
Global access point: Provides a centralized and consistent way to access the single instance.
Resource management: Ensures only one instance exists, which can be helpful for managing resources like file handles or database connections.
Enforces single instance: Guarantees that there's only one instance of the class, preventing conflicts or inconsistencies.
Drawbacks of Singleton Pattern
Tight coupling: Code that relies on the Singleton class becomes tightly coupled to it, making testing and refactoring more challenging.
Global state: The Singleton instance holds global state, which can be difficult to manage and reason about in complex applications.
Limited flexibility: It might be difficult to create multiple instances for testing or special scenarios.
Example
Factory Method Pattern
Description
The Factory Method Pattern is a creational design pattern that provides an interface for creating objects in a superclass but allows subclasses to alter the type of objects that will be created. It defines an interface for creating an object, but subclasses are responsible for instantiating the appropriate concrete class. This pattern promotes loose coupling between the creator and the created objects, allowing the creator to defer object creation to its subclasses.
Imagine a scenario where you need to create different types of objects based on certain conditions, but you want to keep the client code (code that uses the objects) decoupled from the specific object creation logic. The Factory Method Pattern achieves this by:
Defining a Factory Interface: This interface declares a method (often named
createProduct()
) that all concrete factory classes must implement. This method returns an instance of a product (the object to be created).Creating Concrete Factory Classes: These classes implement the
FactoryInterface
and provide specific implementations for thecreateProduct()
method. Each concrete factory creates a specific type of product.Client Code Uses Factory: The client code interacts with a factory object (often obtained through a static method or dependency injection) and calls the
createProduct()
method to get the desired product instance. It doesn't need to know the concrete class of the product being created.
Benefits of Factory Method Pattern
Decouples object creation: Separates object creation logic from the client code, promoting loose coupling.
Flexibility in object creation: Allows for creating different types of objects based on runtime conditions.
Open/Closed Principle compliance: New concrete factories can be added without modifying existing client code.
Drawbacks of Factory Method Pattern
Introduces an extra layer of abstraction: The factory classes add complexity compared to direct object creation.
Might not be necessary for simple scenarios: If only a few object types exist, simpler approaches might be sufficient.
Example 1
Example 2
Create a Shape interface and concrete classes implementing the Shape interface.
The Factory Method Pattern allows for flexible object creation by delegating the responsibility of creating objects to subclasses, making the system more extensible and maintainable. Each subclass can determine the type of object to create based on its specific requirements.
Abstract Factory Pattern
The Abstract Factory Pattern is a creational design pattern that builds upon the Factory Method Pattern. It provides a way to create families of related objects without specifying their concrete types. The Abstract Factory Pattern is useful when we need to create multiple families of related objects or when we want to provide a level of abstraction for creating objects.
Imagine we have a scenario where we need to create multiple objects that belong to a specific family (e.g., a family of shapes like circle, square, or a family of UI elements like button, checkbox). The Abstract Factory Pattern helps create these families without exposing the concrete implementation details. Here's the approach:
Defining an Abstract Factory Interface: This interface declares methods for creating each product within the family (e.g.,
createShape()
andcreateColor()
for shapes and colors).Creating Concrete Factory Classes: These classes implement the
AbstractFactoryInterface
and provide specific implementations for creating each product in the family. Each concrete factory creates a related set of products (e.g., a ShapeFactory might create Circle and Square, while a UIFactory might create Button and Checkbox).Client Code Uses Factory: The client code interacts with a concrete factory object (often obtained through a static method or dependency injection) and calls the specific create methods to get the desired product instances. Similar to the Factory Method Pattern, the client doesn't need to know the concrete class of the products being created.
Benefits of Abstract Factory Pattern
Creates families of objects: Ensures consistent creation of related objects within a family.
Decouples object creation: Separates object creation logic from the client code, promoting loose coupling.
Flexibility in product types: Allows for creating different families of objects (shapes and colors, UI elements and data providers) without modifying existing code.
Drawbacks of Abstract Factory Pattern
More complex: Introduces additional abstraction compared to the Factory Method Pattern.
Might be overkill for simple scenarios: If you only need to create a few unrelated objects, simpler approaches might be more suitable.
Example 1
Consider a drawing application with shapes (circle, square) and colors (red, green). The Abstract Factory Pattern can be used to create these objects together. The ShapeColorFactory
interface defines methods for creating shapes and colors. Concrete factories (CircleRedFactory
, etc.) implement the interface and create related shapes and colors. The client code (Drawing
) uses a factory object to get both a shape and a color without knowing their concrete classes.
Example 2
We have abstract product interfaces Button
and TextField
representing UI components. And concrete product classes LightThemeButton
, DarkThemeButton
, LightThemeTextField
, and DarkThemeTextField
that implement the abstract product interfaces with specific implementations for each theme. We have an abstract factory interface GUIFactory
with factory methods createButton()
and createTextField()
for creating UI components. Also, we created concrete factory classes LightThemeFactory
and DarkThemeFactory
that implement the abstract factory interface and provide implementations for creating UI components for the Light Theme and Dark Theme, respectively.
We create instances of LightThemeFactory
and DarkThemeFactory
to represent factories for creating UI components for the Light Theme and Dark Theme, respectively. We use the factory methods createButton()
and createTextField()
to create UI components for the respective themes. The concrete factory classes (LightThemeFactory
and DarkThemeFactory
) internally handle the creation of UI components based on the specific theme.
Difference between Factory Method Pattern and Abstract Factory Pattern
Focus
Single type or related types
Families of related objects
Structure
Factory interface + Concrete Factories
Abstract Factory + Concrete Factories
Client Interaction
Calls factory to get a product
Calls factory to get products from a family
Benefits
Decoupling, Flexibility
Consistency, Flexibility, Multiple Families
Number of Methods
Typically includes a single factory method for creating a single type of object.
Typically includes multiple factory methods for creating different types of related objects.
Drawbacks
Extra abstraction, Simple cases
More complex, Overkill for simple cases
Builder Pattern
Description
The Builder Pattern is a creational design pattern that separates the construction of a complex object from its representation, allowing the same construction process to create different representations. It is useful when you need to create an object with many optional parameters or a complex construction process, and you want to keep the construction logic separate from the actual object construction. The Builder Pattern promotes fluent and readable object creation code by providing a step-by-step approach to constructing objects.
Imagine if we have a complex object with many optional parameters. Creating such objects directly with a large constructor can be cumbersome and error-prone. The Builder Pattern provides a solution by:
Defining a Builder Class: This class has methods for setting optional and mandatory properties of the object being constructed. Each method typically returns the builder object itself, allowing for method chaining (calling multiple builder methods consecutively).
Building the Object: The builder class also provides a
build()
method that takes the partially constructed object and returns the final, immutable object.
Example
A computer can have various optional components, such as a CPU, RAM, storage, graphics card, etc. We can use the Builder Pattern to create a ComputerBuilder class with methods for configuring each component, and a Computer class to represent the final constructed computer. The build()
method constructs and returns the final Computer
object using the specified components.
By using Lombok's @Builder
annotation, we can achieve the same functionality as the manually written builder class with less code. Lombok takes care of generating the builder class and its methods, reducing the need for boilerplate code. This makes the code cleaner, more concise, and easier to maintain.
Prototype Pattern
Description
The Prototype Pattern is a creational design pattern that allows to create new objects by copying an existing object, known as the prototype, instead of creating new instances from scratch. It is useful when the construction of new objects is more expensive or complex, and you want to avoid the overhead of repeated object creation by reusing existing instances. The Prototype Pattern promotes object creation by cloning existing objects, providing a convenient and efficient way to create new objects with similar properties.
Imagine if we have an object with a lot of internal data or complex initialization logic. Creating new instances of this object can be expensive or time-consuming. The Prototype Pattern addresses this by:
Defining an Interface (Optional): An interface (often named
Cloneable
) can be used to mark classes that support cloning. However, Java doesn't enforce interfaces for implementing the pattern.Implementing the
clone()
Method: The class representing the prototype object implements theclone()
method. This method typically performs a shallow copy of the object's state, creating a new object with the same values but independent references to mutable member variables.
Benefits of Prototype Pattern
Improved performance: Reduces the cost of creating new objects, especially for complex objects.
Reduces memory usage: By reusing existing objects, the prototype pattern can help optimize memory usage.
Customization: You can modify the cloned object after creation to achieve variations.
Drawbacks of Prototype Pattern
Shallow copy issues: Be mindful of shallow copying and handle references to mutable objects within the prototype appropriately (e.g., by deep copying them).
Complexity for mutable objects: Managing the state of mutable objects within the prototype requires careful consideration.
Limited use cases: Not all objects are suitable for cloning. The pattern might be unnecessary for simple objects.
When to Use Prototype Pattern
The Prototype Pattern is suitable when:
We need to create multiple copies of an object that is expensive or complex to create.
We want to customize newly created objects based on an existing one.
Example 1
Let's consider a example of a document editor application. Users can create documents with various formatting styles, such as font size, font style, and alignment. We can use the Prototype Pattern to create a prototype document object and then clone it to create new documents with similar properties.
We have a prototype class Document
representing a document with content and formatting styles. The Document
class implements the Cloneable
interface to indicate that it supports cloning. We override the clone()
method to create a deep copy of the Document
object, including its content and styles. We then modify the content and styles of the new documents independently without affecting the prototype or other clones.
Example 2
Consider a game where we need to create multiple enemies of the same type (e.g., Orcs). Instead of creating each Orc from scratch, you can use a prototype.
The Orc
class implements Cloneable
(optional). The clone()
method creates a new Orc
object with the same values as the prototype. The clone()
method typically performs a shallow copy, meaning references to objects within the prototype (like the Weapon
here) are also copied by reference, not by value.
Last updated
Was this helpful?