Mastering Python Design Patterns: Enhance Your Coding Skills
Written on
Chapter 1: Introduction to Design Patterns
Design patterns serve as solutions to common issues in software development, assisting developers in crafting code that is maintainable, testable, and scalable. Python, known for its versatility and readability, is particularly suited for implementing these classic design patterns. A key concept in achieving flexibility and abstraction is polymorphism, which allows objects to take on various forms.
In this article, we will delve into four essential design patterns — Factory, Template Method, Command, and Observer — and illustrate how polymorphism is fundamental to each. Throughout, we will emphasize best practices in Pythonic idioms and style.
Section 1.1: The Factory Pattern
The Factory Pattern encapsulates the creation of families of related or dependent objects, promoting loose coupling and concealing complex construction logic. Polymorphism enables the selection of desired products at runtime.
Example: ShapeFactory
from abc import ABC, abstractmethod
class Shape(ABC):
@abstractmethod
def draw(self):
pass
class Rectangle(Shape):
def draw(self):
print("Drawing rectangle")
class Square(Shape):
def draw(self):
print("Drawing square")
class Circle(Shape):
def draw(self):
print("Drawing circle")
class ShapeFactory(object):
@staticmethod
def create_shape(shape_type):
if shape_type == "rectangle":
return Rectangle()elif shape_type == "square":
return Square()elif shape_type == "circle":
return Circle()else:
raise ValueError("Invalid shape type")
if __name__ == "__main__":
for shape in ["rectangle", "square", "circle"]:
ShapeFactory.create_shape(shape).draw()
Output:
Drawing rectangle
Drawing square
Drawing circle
Section 1.2: The Template Method Pattern
The Template Method pattern defines the steps of an algorithm while deferring some of these steps to subclasses. It establishes invariant sequences, ensures consistency, and prevents code duplication. Variations of the algorithm arise through extension while maintaining the original structure.
Example: OrderProcessing
class PaymentProcessor(metaclass=ABCMeta):
@abstractmethod
def pay(self, amount):
pass
class BankTransferPaymentProcessor(PaymentProcessor):
def pay(self, amount):
print(f"Paying ${amount} via bank transfer")
class CreditCardPaymentProcessor(PaymentProcessor):
def pay(self, amount):
print(f"Charging ${amount} to credit card")
class Order(object):
def __init__(self, payment_processor: PaymentProcessor):
self.payment_processor = payment_processor
def place(self, total):
self.payment_processor.pay(total)
print("Order placed successfully")
if __name__ == "__main__":
order = Order(BankTransferPaymentProcessor())
order.place(100.0)
order = Order(CreditCardPaymentProcessor())
order.place(100.0)
Output:
Paying $100.00 via bank transfer
Order placed successfully
Charging $100.00 to credit card
Order placed successfully
Chapter 2: The Command Pattern
The Command Pattern decouples the requester from the receiver, transforming actions into independent objects. Commands encapsulate invocation while encoding metadata, enabling undo/redo capabilities and composition. Invokers maintain references to commands without needing to know about the receivers.
Example: LightController
class SwitchOnCommand(object):
def __init__(self, device):
self.device = device
def execute(self):
self.device.switch_on()
class SwitchOffCommand(object):
def __init__(self, device):
self.device = device
def execute(self):
self.device.switch_off()
class Device(object):
def switch_on(self):
print("Device turned on")
def switch_off(self):
print("Device turned off")
class RemoteControl(object):
def __init__(self):
self.commands = []
def set_command(self, command):
self.commands.append(command)
def press_button(self):
for cmd in self.commands:
cmd.execute()
if __name__ == "__main__":
lamp = Device()
controller = RemoteControl()
controller.set_command(SwitchOnCommand(lamp))
controller.press_button()
controller.set_command(SwitchOffCommand(lamp))
controller.press_button()
Output:
Device turned on
Device turned off
Chapter 3: The Observer Pattern
The Observer Pattern creates a connection between subjects and their dependents, facilitating the propagation of state updates. Subjects notify dependents of state changes, prompting them to update themselves accordingly. This loose coupling allows for the addition or removal of dependents without impacting the subjects.
Example: StockTicker
class Observer(object):
def update(self, stock):
pass
class Observable(object):
def __init__(self):
self.observers = []
def register(self, observer):
self.observers.append(observer)
def remove(self, observer):
self.observers.remove(observer)
def notify(self, price):
for obs in self.observers:
obs.update(price)
class StockObserver(Observer):
def update(self, stock):
print(f"Stock updated to {stock.price}")
class Stock(Observable):
def __init__(self, symbol):
super().__init__()
self.symbol = symbol
self.price = 0
def attach(self, observer):
self.register(observer)
def detach(self, observer):
self.remove(observer)
def updatePrice(self, price):
old_price = self.price
self.price = price
self.notify(price)
if __name__ == "__main__":
apple = Stock("AAPL")
google = Stock("GOOG")
investor = StockObserver()
apple.attach(investor)
google.attach(investor)
apple.updatePrice(150)
google.updatePrice(1000)
Output:
Stock updated to 150
Stock updated to 1000
Conclusion
Utilizing polymorphism alongside timeless design patterns empowers developers to combat code complexity effectively. Coupled with Python's inherent readability and simplicity, creating robust, maintainable solutions becomes a fundamental skill. Embrace these principles widely and share the knowledge of quality coding practices with peers and future developers.
The first video, "Master Python Design Patterns: Build Flexible & Robust Code," provides a comprehensive overview of design patterns in Python, demonstrating their practical applications.
The second video, "Why Use Design Patterns When Python Has Functions?" explores the rationale behind using design patterns in Python, despite its functional capabilities.