What are Creational Patterns and where are they used?
Creational Patterns are a group of design patterns that deal with common object-creation problems faced by the developers. They provide ways to create objects in a manner that is more flexible, efficient, organised and suitable to the situation.
They are used in a wide variety of software development projects, from web applications, desktop applications, and mobile applications, to games. They are also used in complex systems where there is a need to create complex interrelated components or objects.
When to Use Creational Patterns?
Creational patterns should be used when there is a need to create complex objects or when there is a need for flexibility in object creation. They should not be used when object creation is straightforward.
How many patterns are there in Creational Patterns?
Creational design patterns are divided into six patterns: Simple Factory, Abstract Factory, Builder, Factory Method, Prototype, and Singleton. Let's look at each of these patterns and how to implement them in Python.
Simple Factory Pattern
The Simple Factory pattern provides a way to create objects without specifying the exact class of the object that will be created behind the scenes. It involves a single factory class responsible for creating objects based on the provided parameters or input.
Here's a breakdown of the key components of the Simple Factory pattern:
Factory Class: This class contains a method or methods responsible for creating objects. It typically has a switch or if-else logic to determine which type of object to instantiate based on the provided parameters or input. This acts as the main gateway which will be used by the programmers to create objects throughout the project (basically, the client code) once the pattern has been implemented.
Product Interface: All the products (objects) created by the factory implement a common interface (or, in the world of Python, extend a common abstract class). This ensures that the client code can treat the objects uniformly through the rules enforced by the interface or abstract class without having to know the underlying object created by the factory class.
Concrete Products: Classes that implement the abstract product interfaces or extend the abstract product classes. These are the actual objects created by the concrete factories.
Client Code: The code that needs an object and doesn't directly instantiates it. Instead, it relies on the factory class to create the object based on the specified parameters.
When to use the Simple Factory Pattern?
Use a simple factory pattern when making an object is not simple. If there's some logic or a series of steps required to decide exactly which object to create, then, it's better to encapsulate that process in a dedicated factory rather than duplicating the same code throughout your project.
Example implementation in Python
Let's consider a simplified example of creating different models of cars in the context of Toyota's car manufacturing plant.
from abc import ABC, abstractmethod
# Product interface or Abstract Class
class Car(ABC):
@abstractmethod
def assemble(self): pass
# Concrete products
class Corolla(Car):
def assemble(self):
return "Assembling Toyota Corolla"
class Camry(Car):
def assemble(self):
return "Assembling Toyota Camry"
# Simple Factory
class ToyotaCarFactory:
@staticmethod
def create_car(model: str) -> Car:
if model == "Corolla":
return Corolla()
elif model == "Camry":
return Camry()
else:
raise ValueError("Invalid car model")
# Client code
def produce_car():
car = ToyotaCarFactory.create_car(model="Corolla")
corolla_assembly = car.assemble()
car = ToyotaCarFactory.create_car(model="Camry")
camry_assembly = car.assemble()
print(corolla_assembly) # Output: "Assembling Toyota Corolla"
print(camry_assembly) # Output: "Assembling Toyota Camry"
# Example usage
produce_car()
In this example:
Product Interface (
Car
): Represents the common interface for different car models.Concrete Products (
Corolla
,Camry
): Implement theCar
abstract class, representing specific models of Toyota cars.Simple Factory (
ToyotaCarFactory
): Has a method (create_car
) responsible for creating instances of concrete car products based on the provided car model. It allows to create cars based on orders without exposing the complexities of the car assembly process to the order processing system and encapsulates the logic for creating different models of cars in a single factory class.Client Code (
produce_car
): The ToyotaCarFactory is used to create cars without having to know the details of the car instantiation. It specifies the desired car model, and the factory creates the corresponding car.
The Simple Factory pattern in this context provides a clean and modular way to manage the creation of various car models, making it easier to add new models in the future without modifying the client code that orders the cars.
Abstract Factory Pattern
The Abstract Factory pattern provides an interface for creating families of related or dependent objects without specifying their concrete classes. In other words, it is a factory that groups related or dependent factories together (simply put, a factory of factories).
Here's a breakdown of the key components of the Abstract Factory pattern:
Abstract Factory Interface: An interface or an abstract class that declares a set of methods to create a family of related objects. Each method in this interface typically corresponds to the creation of a particular type of object.
Concrete Factories: Classes that implement the abstract factory interface. Each concrete factory is responsible for creating a family of related or dependent objects (or products).
Product Interfaces: Same concept as explained in the Simple Factory pattern.
Concrete Products: Same concept as explained in the Simple Factory pattern.
Client Code: Same concept as explained in the Simple Factory pattern.
When to use the Abstract Factory Pattern?
The Abstract Factory pattern can be used in scenarios when there is a need to create a family of multiple related objects which are meant to be used together or have dependencies on each other but there are several such families to choose from and we need a run-time value from the client code to decide which of these family to instantiate.
However, it is important to note that the Abstract Factory pattern should not be used when there is only one family of objects. In such cases, the Factory Method pattern can be used instead.
Example implementation in Python
Extending the previous example of a Toyota car manufacturing plant, let's delve into the Abstract Factory pattern using a real-world scenario.
from abc import ABC, abstractmethod
# Abstract Products
class Sedan(ABC):
@abstractmethod
def drive(self): pass
class SUV(ABC):
@abstractmethod
def drive(self): pass
class Hybrid(ABC):
@abstractmethod
def drive(self): pass
# Concrete Products for Toyota cars
class Camry(Sedan):
def drive(self) -> str:
return "Driving Toyota Camry"
class RAV4(SUV):
def drive(self) -> str:
return "Driving Toyota RAV4"
class Prius(Hybrid):
def drive(self) -> str:
return "Driving Toyota Prius"
# Concrete Products for Lexus cars
class ES(Sedan):
def drive(self) -> str:
return "Driving Lexus ES"
class RX(SUV):
def drive(self) -> str:
return "Driving Lexus RX"
class UX(Hybrid):
def drive(self) -> str:
return "Driving Lexus UX"
# Abstract Factory Interface
class CarFactory(ABC):
@abstractmethod
def create_sedan(self): pass
@abstractmethod
def create_suv(self): pass
@abstractmethod
def create_hybrid(self): pass
# Concrete Factory for Toyota cars
class ToyotaFactory(CarFactory):
def create_sedan(self) -> Camry:
return Camry() # ConcreteProduct1
def create_suv(self) -> RAV4:
return RAV4() # ConcreteProduct2
def create_hybrid(self) -> Prius:
return Prius() # ConcreteProduct3
# Concrete Factory for Lexus cars
class LexusFactory(CarFactory):
def create_sedan(self) -> ES:
return ES() # ConcreteProduct4
def create_suv(self) -> RX:
return RX() # ConcreteProduct5
def create_hybrid(self) -> UX:
return UX() # ConcreteProduct6
# Client Code
class CarShop:
def order_cars(self, factory: CarFactory) -> tuple[str, str, str]:
sedan = factory.create_sedan()
suv = factory.create_suv()
hybrid = factory.create_hybrid()
return sedan.drive(), suv.drive(), hybrid.drive()
# Example usage
car_shop = CarShop()
# Order and drive Toyota cars
toyota_factory = ToyotaFactory()
toyota_order = car_shop.order_cars(toyota_factory)
# Order and drive Lexus cars
lexus_factory = LexusFactory()
lexus_order = car_shop.order_cars(lexus_factory)
print(toyota_order)
# Output: ('Driving Toyota Camry', 'Driving Toyota RAV4', 'Driving Toyota Prius')
print(lexus_order)
# Output: ('Driving Lexus ES', 'Driving Lexus RX', 'Driving Lexus UX')
In this example:
Abstract Factory Interface (
CarFactory
): Represents an interface or abstract class declaring the methods for creating families of related objects (cars in this case).Concrete Factories (
ToyotaFactory
,LexusFactory
): Classes implementing theCarFactory
interface. Each concrete factory is responsible for creating a family of related car models.Product Interfaces (
Sedan
,SUV
,Hybrid
): Abstract classes or interfaces declaring the common methods for different types of cars.Concrete Products (
Camry
,RAV4
,Prius
,ES
,RX
,UX
): Classes implementing the abstract product interfaces representing specific models of Toyota and Lexus cars.Client Code (
CarShop
): The code that utilizes the abstract factory to create families of cars and interacts with cars through their common interfaces (Abstract Products). Run-time value of any concrete factory is required, which implements the abstract factory interface and does not depend on which of the family (concrete factory) is passed to it.
This structure allows the Toyota and Lexus factories to produce different models of cars (sedans, SUVs, hybrids) with a consistent interface. The client code (CarShop) can order and drive cars without needing to know the specific details of each car model's creation process.
The Abstract Factory pattern promotes loose coupling between objects. Because the client code is only dependent on the abstract factory interface and not on the concrete classes of the objects, it is easier to modify the object creation process in the future without affecting the client code.
Factory Method Pattern
The basic idea of the Factory Method pattern is to model an abstract class or interface for creating objects, and then let subclasses decide which concrete classes to instantiate. This allows the code to be more flexible and extensible, as new subclasses can be created without changing the core code.
So, essentially the Factory Method pattern is the opposite approach to the Simple Factory pattern. In the Factory Method pattern, the responsibility for creating objects is delegated to subclasses that extend the factory class(abstract class or interface for creating objects). Whereas, in the Simple Factory pattern, a single factory class, which encapsulates the instantiation of objects, is responsible for creating objects based on input parameters.
The benefits of using the Factory Method pattern are Flexibility and Extensibility. The Factory Method pattern allows for the creation of new subclasses without changing the existing code, which makes the code more extensible, flexible and easier to maintain.
Here's a breakdown of the key components of the Factory Method pattern:
Creator Interface: An interface or an abstract class that declares the factory method that creates (or returns) an object of a class that implements the Product Interface. The factory method may sometimes have a default implementation, instead of just declaration, in cases where we want to return a default Concrete Product. The creator interface may also include other methods that operate on products based on the scenario.
Concrete Creators: Classes implementing the Creator interface and providing their specific implementation of the factory method to create a particular type of product. Typically, for each Concrete Product there will be a Concrete Creator.
Product Interface: Same concept as explained in the Simple Factory pattern.
Concrete Products: Same concept as explained in the Simple Factory pattern.
Client Code: Same concept as explained in the Simple Factory pattern.
When to use the Factory Method Pattern?
When you're dealing with multiple identical classes that differ in their implementations but share a similar interface, and the concrete classes are decided upon at runtime, use the Factory Method pattern. This approach allows to delegate the responsibility of object creation to subclasses. The Factory Method style implements the open/closed principle by depending on inheritance and allowing subclasses to provide their implementations. This means that new product types can be added without changing the client code that already exists.
However, it is important to note that the Factory Method pattern can result in a lot of classes and can be more complex than other creational patterns. It is best suited for situations where there are multiple types of objects.
Example implementation in Python
Let's apply the Factory Method pattern to the Toyota car factory scenario.
from abc import ABC, abstractmethod
# Creator Interface
class CarFactory(ABC):
@abstractmethod
def create_car(self): pass
# Concrete Creators
class CamryFactory(CarFactory):
def create_car(self):
return Camry()
class RAV4Factory(CarFactory):
def create_car(self):
return RAV4()
class PriusFactory(CarFactory):
def create_car(self):
return Prius()
# Product Interface
class Car(ABC):
@abstractmethod
def drive(self): pass
# Concrete Products
class Camry(Car):
def drive(self) -> str:
return "Driving Toyota Camry"
class RAV4(Car):
def drive(self) -> str:
return "Driving Toyota RAV4"
class Prius(Car):
def drive(self) -> str:
return "Driving Toyota Prius"
# Client Code
class CarShop:
def order_car(self, factory):
car = factory.create_car()
result = car.drive()
return result
# Example Usage
camry_factory = CamryFactory()
rav4_factory = RAV4Factory()
prius_factory = PriusFactory()
shop = CarShop()
camry_result = shop.order_car(camry_factory)
rav4_result = shop.order_car(rav4_factory)
prius_result = shop.order_car(prius_factory)
print(camry_result)
# Ouput: Driving Toyota Camry
print(rav4_result)
# Ouput: Driving Toyota RAV4
print(prius_result)
# Ouput: Driving Toyota Prius
In this example:
Creator Interface (
CarFactory
): An interface or abstract class declaring the factory method for creating cars.Concrete Creators (
CamryFactory
,RAV4Factory
,PriusFactory
): Classes implementing the abstract classCarFactory
and providing the factory method implementation to create specific models of cars.Product Interface (
Car
): Abstract classes or interfaces declaring the common method, drive, for different car models.Concrete Products (
Camry
,RAV4
,Prius
): Classes implementing the abstract product interfaces representing specific car models.Client Code (
CarShop
): The code that utilizes the factory method to create and order cars without knowing the specific details of each car model's creation process.
The Factory Method pattern allows the client code (CarShop) to depend on the abstract CarFactory
interface for car creation, while the concrete factories (CamryFactory
, RAV4Factory
, PriusFactory
) handle the instantiation of specific car models. This pattern promotes loose coupling and allows for easy extension when introducing new car models without modifying existing client code.
Builder Pattern
The Builder pattern allows us to create personalized objects by configuring their properties one by one. The idea is to separate the construction of an object from its representation so that the same construction process can create different representations. In simple words, the idea is to separate the configuration and functionality logic into separate classes.
The Builder pattern involves creating a separate object called the "builder" that is responsible for building the personalized object. The builder provides a set of configurations, in the form of methods, that allow us to tweak the object based on the scenario. Once the builder object is configured it can be used to create the final object with all the desired configurations.
When to use the Builder Pattern?
The Builder pattern is often used when the creation of a complex object involves a lot of steps or involves different variations of the same object. Since the configuration logic of the object is encapsulated in the builder object it allows us to add new configurations without having to change the construction process. This makes the code more flexible, easier to modify and maintain. Existing builder object can be reused to apply different modifications to create new final objects, which reduces code duplication.
The simplest way to identify if a builder pattern can be applied in a scenario is when the constructor of the object has a lot of parameters in it.
However, the builder pattern is only suited for situations where the construction of the object is complex and involves a lot of steps, unnecessary use of the pattern may result in. For simpler objects, the Factory Method pattern may be more appropriate.
Example implementation in Python
In the context of a Toyota car manufacturing plant, let's explore the Builder pattern using a real-world scenario:
Imagine you're tasked with building various models of Toyota cars, each with different features and configurations. The Builder pattern comes in handy by allowing you to construct these cars step by step, ensuring flexibility and customization.
from typing import List
class Car:
def __init__(self):
self.model: str = None
self.engine: str = None
self.color: str = None
self.options: List[str] = []
def __str__(self) -> str:
return f"{self.color} Toyota {self.model} with options {self.options}"
class CarBuilder:
def __init__(self):
self.car: Car = Car()
def build_model(self, model: str) -> 'CarBuilder':
self.car.model = model
return self
def build_engine(self, engine: str) -> 'CarBuilder':
self.car.engine = engine
return self
def paint(self, color: str) -> 'CarBuilder':
self.car.color = color
return self
def add_option(self, option: str) -> 'CarBuilder':
self.car.options.append(option)
return self
def build(self) -> Car:
return self.car
class CarDirector:
@staticmethod
def construct_Camry(builder: CarBuilder) -> Car:
return builder.build_model("Camry").build_engine("V6").paint("Red").add_option("Leather seats").build()
@staticmethod
def construct_RAV4(builder: CarBuilder) -> Car:
return builder.build_model("RAV4").build_engine("Hybrid").paint("Black").add_option("Leather seats").add_option("Sunroof").build()
# Client code
camry = CarDirector.construct_Camry(CarBuilder())
print(camry)
# Output: Red Toyota Camry with options ['Leather seats']
rav4 = CarDirector.construct_RAV4(CarBuilder())
print(rav4)
# Output: Black Toyota RAV4 with options ['Leather seats', 'Sunroof']
In this example:
Product (
Car
): Represents a Toyota car, this is the complex object that is being built. It has attributes like model, engine, color, and a list of other features.Builder (
CarBuilder
): An abstract class or interface defining the methods to build different parts of the Product object (car). It allows setting the model, color, and adding various options to the car. The methods are designed to be chainable, allowing for a fluent interface.Director: The
CarDirector
class is an optional component, responsible for orchestrating the construction process using a specific builder. It defines the order in which to execute the building steps to construct the products. This might be used to define specific presets or configurations.Client Code: Using the Builder pattern to create different models of Toyota cars. The client creates a builder object, directs (optional) the construction process, and creates customized instances of Toyota cars, allowing for flexibility and variation in the construction process.
Prototype Pattern
The Prototype pattern involves creating new objects by copying an existing object, known as the prototype. This pattern is particularly effective in scenarios where the instantiation of a class is costly or complex and the new object required is just slightly different from the existing object.
Here's a breakdown of the key components of the Prototype pattern:
Prototype Interface: An interface that defines the cloning method.
Concrete Prototype: A class implementing the prototype interface and defining the method to clone itself.
Client: The part of the application that uses the prototype to create new objects.
When to Use the Prototype Pattern?
Use the Prototype pattern when a system requires new objects that are similar to existing objects and creating objects from scratch is less efficient than just copying existing objects, or when the initialization of an object is more complex than just configuring its state.
Example Implementation in Python
In India, Maruti Suzuki and Toyota had a collaboration that involved the sharing of certain car models. The Baleno and Glanza are examples of such collaboration. The two cars are essentially the same but with slight differences in terms of branding and some features. Here, the Prototype pattern can be effectively used.
from abc import ABC, abstractmethod
from copy import deepcopy
class CarPrototype(ABC):
@abstractmethod
def clone(self): pass
# Concrete Prototypes
class Baleno(CarPrototype):
def __init__(self):
self.model = "Baleno"
self.displacement = "1197cc"
self.max_power = "88.50bhp@6000rpm"
self.max_torque = "113Nm@4400rpm"
self.transmission = "Automatic"
self.features = [
"Sunroof",
"Standard Baleno Features"
]
def get_info(self):
return f"Model: {self.model}, Features: {', '.join(self.features)}"
def clone(self):
return deepcopy(self)
# Client Code
baleno_prototype = Baleno()
print(baleno_prototype.get_info())
# Output: Model: Baleno, Features: Sunroof, Standard Features for Baleno
# Toyota creates Glanza instances based on Baleno prototype
glanza_instance_1 = baleno_prototype.clone()
glanza_instance_1.model = "Glanza"
glanza_instance_1.features.append("Additional Features for Glanza 1")
glanza_instance_2 = baleno_prototype.clone()
glanza_instance_2.model = "Glanza"
glanza_instance_2.features.append("Additional Features for Glanza 2")
print(glanza_instance_1.get_info())
# Output: Model: Glanza, Features: Sunroof, Standard Features for Baleno, Additional Features for Glanza 1
print(glanza_instance_2.get_info())
# Output: Model: Glanza, Features: Sunroof, Standard Features for Baleno, Additional Features for Glanza 2
In this example:
Prototype (
CarPrototype
): An abstract class that enforces an abstract method,clone
, to create a deep copy of the object. This is the core of the Prototype pattern.Concrete Prototype(
Baleno
): This class represents the concrete prototype for the Baleno car model with its standard features.Client: The client code (representing Toyota) creates instances of Glanza by cloning the Baleno prototype and modifying the model and features. This demonstrates how the Prototype pattern allows for easy creation of variations without modifying the original prototype.
The Prototype pattern in this scenario simplifies the creation of new car instances, reducing the cost and complexity of creating each car from scratch.
Singleton Pattern
The Singleton pattern ensures that a class has only one instance and provides a global point of access to it. This pattern is used to control access to resources such as database connections or configuration settings.
Here's a breakdown of the key components of the Factory Method pattern:
Singleton Class: A class that restricts object creation for itself and ensures only one instance is created.
Private Constructor: Prevents other classes from instantiating it.
Static Method: Provides a global access point to the instance and controls its access.
When to Use the Singleton Pattern?
Use the Singleton pattern when there must be exactly one instance of a class, and it must be accessible to clients from a well-known access point, especially in scenarios like managing a database connection or configuring system-wide settings.
Example Implementation in Python
class LoggerSingleton:
__instance = None
@staticmethod
def getInstance():
if LoggerSingleton.__instance is None:
LoggerSingleton()
return LoggerSingleton.__instance
def __init__(self):
if LoggerSingleton.__instance is not None:
raise Exception("This class is a singleton!")
else:
LoggerSingleton.__instance = self
self.log_file = open("log.txt", "a")
def log(self, message):
self.log_file.write(message + '\n')
self.log_file.flush()
# Client Code
logger = LoggerSingleton.getInstance()
logger.log("Starting car assembly.")