Introduction
Design Patterns are reusable solutions to common problems in software design. The Gang of Four (GoF)—Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides—introduced 23 classic design patterns in their book "Design Patterns: Elements of Reusable Object-Oriented Software" (1994).
These patterns are categorized into three main types:
- Creational – Deal with object creation mechanisms.
- Structural – Concerned with object composition and relationships.
- Behavioral – Define communication between objects.
Creational Design Patterns
Creational design patterns deal with object creation mechanisms, trying to create objects in a manner suitable to the situation. They help make a system independent of how its objects are created, composed, and represented.
Singleton Pattern
Ensures that a class has only one instance and provides a global point of access to it.
When to use
- When exactly one instance of a class is needed (e.g., database connection, logging service).
- To control shared resources.
Python Example
class Singleton:
_instance = None # Class-level variable to store the single instance
def __new__(cls):
"""Override __new__ to control instance creation."""
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
# Usage
s1 = Singleton()
s2 = Singleton()
print(s1 is s2) # True (Both variables point to the same instance)
Pros & Cons
✅ Memory efficient (only one instance exists)
❌ Global state (can make testing harder)
Factory Method Pattern
Defines an interface for creating objects, but lets subclasses decide which class to instantiate.
When to use
- When a class can't anticipate the type of objects it needs to create.
- When you want to delegate instantiation to subclasses.
Python Example
from abc import ABC, abstractmethod
class Vehicle(ABC):
"""Abstract base class for vehicles."""
@abstractmethod
def drive(self):
pass
class Car(Vehicle):
def drive(self):
return "Driving a car!"
class Bike(Vehicle):
def drive(self):
return "Riding a bike!"
class VehicleFactory(ABC):
"""Abstract factory that defines the factory method."""
@abstractmethod
def create_vehicle(self) -> Vehicle:
pass
class CarFactory(VehicleFactory):
"""Concrete factory for creating cars."""
def create_vehicle(self) -> Vehicle:
return Car()
class BikeFactory(VehicleFactory):
"""Concrete factory for creating bikes."""
def create_vehicle(self) -> Vehicle:
return Bike()
# Usage
car_factory = CarFactory()
car = car_factory.create_vehicle()
print(car.drive()) # Output: "Driving a car!"
Pros & Cons
✅ Loose coupling (client code doesn’t depend on concrete classes)
❌ Can lead to many subclasses
Abstract Factory Pattern
Provides an interface for creating families of related objects without specifying their concrete classes.
When to use
- When a system needs to be independent of how its products are created.
- When working with multiple product families (e.g., UI components for different OS).
Python Example
from abc import ABC, abstractmethod
# Abstract Products
class Button(ABC):
@abstractmethod
def render(self):
pass
class Checkbox(ABC):
@abstractmethod
def render(self):
pass
# Concrete Products (Windows)
class WindowsButton(Button):
def render(self):
return "Windows Button"
class WindowsCheckbox(Checkbox):
def render(self):
return "Windows Checkbox"
# Concrete Products (Mac)
class MacButton(Button):
def render(self):
return "Mac Button"
class MacCheckbox(Checkbox):
def render(self):
return "Mac Checkbox"
# Abstract Factory
class GUIFactory(ABC):
@abstractmethod
def create_button(self) -> Button:
pass
@abstractmethod
def create_checkbox(self) -> Checkbox:
pass
# Concrete Factories
class WindowsFactory(GUIFactory):
def create_button(self) -> Button:
return WindowsButton()
def create_checkbox(self) -> Checkbox:
return WindowsCheckbox()
class MacFactory(GUIFactory):
def create_button(self) -> Button:
return MacButton()
def create_checkbox(self) -> Checkbox:
return MacCheckbox()
# Usage
def create_ui(factory: GUIFactory):
button = factory.create_button()
checkbox = factory.create_checkbox()
print(button.render(), checkbox.render())
# Create Windows UI
create_ui(WindowsFactory()) # Output: "Windows Button Windows Checkbox"
# Create Mac UI
create_ui(MacFactory()) # Output: "Mac Button Mac Checkbox"
Pros & Cons
✅ Ensures product compatibility
❌ Complex to implement
Builder Pattern
Separates the construction of a complex object from its representation, allowing the same construction process to create different representations.