Explore the world of creational design patterns, learn how they simplify object creation, and discover practical examples in Python and JavaScript.
In the realm of software design, creational patterns play a pivotal role in managing the complexities of object creation. These patterns provide a structured approach to instantiating objects, making your code more flexible, reusable, and easier to maintain. This section delves into the core concepts of creational patterns, their purpose, and their common implementations in modern programming languages like Python and JavaScript.
Creational patterns are design patterns that deal with object creation mechanisms. They abstract the instantiation process, allowing for more flexibility in deciding which objects need to be created for a given scenario. By encapsulating the creation logic, creational patterns help in managing the complexities associated with creating objects, especially when dealing with complex object hierarchies or dependencies.
The primary purpose of creational patterns is to decouple the client code from the concrete classes it needs to instantiate. This decoupling provides several benefits:
Let’s explore some of the most common creational patterns and understand how they contribute to effective software design.
The Singleton pattern ensures that a class has only one instance and provides a global point of access to that instance. This pattern is useful when exactly one object is needed to coordinate actions across the system.
Example in Python:
class SingletonMeta(type):
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
instance = super().__call__(*args, **kwargs)
cls._instances[cls] = instance
return cls._instances[cls]
class Singleton(metaclass=SingletonMeta):
def some_business_logic(self):
pass
singleton1 = Singleton()
singleton2 = Singleton()
assert singleton1 is singleton2
Explanation:
SingletonMeta
metaclass ensures that only one instance of the Singleton
class is created.__call__
method checks if an instance already exists; if not, it creates one.singleton1
and singleton2
refer to the same instance, demonstrating the Singleton pattern.The Factory Method pattern defines an interface for creating an object but lets subclasses alter the type of objects that will be created. This pattern is particularly useful for scenarios where a class cannot anticipate the class of objects it must create.
Example in Python:
from abc import ABC, abstractmethod
class Button(ABC):
@abstractmethod
def render(self):
pass
class WindowsButton(Button):
def render(self):
print("Render a button in Windows style")
class MacOSButton(Button):
def render(self):
print("Render a button in MacOS style")
class Dialog(ABC):
@abstractmethod
def create_button(self):
pass
def render(self):
button = self.create_button()
button.render()
class WindowsDialog(Dialog):
def create_button(self):
return WindowsButton()
class MacOSDialog(Dialog):
def create_button(self):
return MacOSButton()
dialog = WindowsDialog()
dialog.render()
Explanation:
Button
and Dialog
are abstract classes that define the interface for buttons and dialogs, respectively.WindowsButton
and MacOSButton
are concrete implementations of the Button
interface.create_button
is the factory method in the Dialog
class that is overridden by subclasses WindowsDialog
and MacOSDialog
to create specific button instances.WindowsDialog
class creates a WindowsButton
, demonstrating the Factory Method pattern.Class Diagram:
classDiagram class Button { +render() } class WindowsButton { +render() } class MacOSButton { +render() } Button <|-- WindowsButton Button <|-- MacOSButton class Dialog { +create_button() +render() } class WindowsDialog { +create_button() } class MacOSDialog { +create_button() } Dialog <|-- WindowsDialog Dialog <|-- MacOSDialog Dialog o--> Button
The Abstract Factory pattern provides an interface for creating families of related or dependent objects without specifying their concrete classes. It is a super-factory that creates other factories, encapsulating a group of individual factories with a common theme.
Example in Python:
from abc import ABC, abstractmethod
class GUIFactory(ABC):
@abstractmethod
def create_button(self):
pass
@abstractmethod
def create_checkbox(self):
pass
class WinFactory(GUIFactory):
def create_button(self):
return WindowsButton()
def create_checkbox(self):
return WindowsCheckbox()
class MacFactory(GUIFactory):
def create_button(self):
return MacOSButton()
def create_checkbox(self):
return MacOSCheckbox()
class Button(ABC):
@abstractmethod
def paint(self):
pass
class WindowsButton(Button):
def paint(self):
print("Windows Button")
class MacOSButton(Button):
def paint(self):
print("MacOS Button")
class Checkbox(ABC):
@abstractmethod
def paint(self):
pass
class WindowsCheckbox(Checkbox):
def paint(self):
print("Windows Checkbox")
class MacOSCheckbox(Checkbox):
def paint(self):
print("MacOS Checkbox")
def client_code(factory: GUIFactory):
button = factory.create_button()
checkbox = factory.create_checkbox()
button.paint()
checkbox.paint()
client_code(WinFactory())
client_code(MacFactory())
Explanation:
GUIFactory
defines the interface for creating related objects like buttons and checkboxes.WinFactory
and MacFactory
implement the GUIFactory
interface to create Windows and MacOS components, respectively.client_code
function uses the factory to create and use GUI components without knowing their specific classes.The Builder pattern separates the construction of a complex object from its representation, allowing the same construction process to create different representations. It is useful when an object needs to be created in multiple steps or when an object has numerous configurations.
Example in Python:
class Car:
def __init__(self):
self.parts = []
def add(self, part):
self.parts.append(part)
def list_parts(self):
print(f"Car parts: {', '.join(self.parts)}")
class Builder(ABC):
@abstractmethod
def produce_part_a(self):
pass
@abstractmethod
def produce_part_b(self):
pass
@abstractmethod
def produce_part_c(self):
pass
class ConcreteBuilder1(Builder):
def __init__(self):
self.reset()
def reset(self):
self._product = Car()
def produce_part_a(self):
self._product.add("PartA1")
def produce_part_b(self):
self._product.add("PartB1")
def produce_part_c(self):
self._product.add("PartC1")
def get_product(self):
product = self._product
self.reset()
return product
class Director:
def __init__(self):
self._builder = None
@property
def builder(self):
return self._builder
@builder.setter
def builder(self, builder):
self._builder = builder
def build_minimal_viable_product(self):
self.builder.produce_part_a()
def build_full_featured_product(self):
self.builder.produce_part_a()
self.builder.produce_part_b()
self.builder.produce_part_c()
builder = ConcreteBuilder1()
director = Director()
director.builder = builder
print("Standard basic product:")
director.build_minimal_viable_product()
builder.get_product().list_parts()
print("Standard full featured product:")
director.build_full_featured_product()
builder.get_product().list_parts()
print("Custom product:")
builder.produce_part_a()
builder.produce_part_c()
builder.get_product().list_parts()
Explanation:
The Prototype pattern specifies the kinds of objects to create using a prototypical instance and creates new objects by copying this prototype. It is useful for creating objects when the cost of creating a new instance is more expensive than copying an existing one.
Example in Python:
import copy
class Prototype:
def __init__(self):
self._objects = {}
def register_object(self, name, obj):
self._objects[name] = obj
def unregister_object(self, name):
del self._objects[name]
def clone(self, name, **attrs):
obj = copy.deepcopy(self._objects.get(name))
obj.__dict__.update(attrs)
return obj
class Car:
def __init__(self):
self.make = "Ford"
self.model = "Mustang"
self.color = "Red"
def __str__(self):
return f"{self.make} {self.model} {self.color}"
prototype = Prototype()
car = Car()
prototype.register_object('ford', car)
car_clone = prototype.clone('ford', color='Blue')
print(car_clone)
Explanation:
Creational patterns offer several benefits that enhance software design:
Creational patterns are fundamental to effective software design, providing a structured approach to object creation that enhances flexibility, maintainability, and reusability. By understanding and applying these patterns, developers can create robust and adaptable software systems that meet the evolving needs of users and businesses.