Introduction
Structural design patterns deal with object composition, helping to ensure that if one part of a system changes, the entire system doesn't need to change. These patterns focus on how objects and classes are combined to form larger structures.
Adapter Pattern
The Adapter pattern allows objects with incompatible interfaces to collaborate by wrapping its own interface around that of an already existing class.
class Adaptee:
def specific_request(self):
return "Specific request from Adaptee"
class Target:
def request(self):
return "Target request"
class Adapter(Target):
def __init__(self):
self.adaptee = Adaptee()
def request(self):
return f"Adapter: {self.adaptee.specific_request()}"
adapter = Adapter()
print(adapter.request()) # Adapter: Specific request from Adaptee
Decorator Pattern
The Decorator pattern attaches additional responsibilities to an object dynamically, providing a flexible alternative to subclassing for extending functionality.
class Coffee:
def cost(self):
return 5
def description(self):
return "Coffee"
class CoffeeDecorator:
def __init__(self, coffee):
self._coffee = coffee
def cost(self):
return self._coffee.cost()
def description(self):
return self._coffee.description()
class Milk(CoffeeDecorator):
def cost(self):
return self._coffee.cost() + 1.5
def description(self):
return self._coffee.description() + ", Milk"
class Sugar(CoffeeDecorator):
def cost(self):
return self._coffee.cost() + 0.5
def description(self):
return self._coffee.description() + ", Sugar"
coffee = Coffee()
coffee_with_milk = Milk(coffee)
coffee_with_milk_and_sugar = Sugar(coffee_with_milk)
print(f"Cost: {coffee_with_milk_and_sugar.cost()}")
print(f"Description: {coffee_with_milk_and_sugar.description()}")
Facade Pattern
The Facade pattern provides a simplified interface to a complex subsystem, making it easier to use.
class CPU:
def freeze(self):
print("CPU: Freezing processor")
def jump(self, position):
print(f"CPU: Jumping to position {position}")
def execute(self):
print("CPU: Executing instructions")
class Memory:
def load(self, position, data):
print(f"Memory: Loading {data} at position {position}")
class HardDrive:
def read(self, sector, size):
print(f"HardDrive: Reading {size} bytes from sector {sector}")
return "boot data"
class ComputerFacade:
def __init__(self):
self.cpu = CPU()
self.memory = Memory()
self.hard_drive = HardDrive()
def start(self):
self.cpu.freeze()
data = self.hard_drive.read(0, 1024)
self.memory.load(0, data)
self.cpu.jump(0)
self.cpu.execute()
def shutdown(self):
print("Computer shutting down")
computer = ComputerFacade()
computer.start()
Practice Problems
- Create an adapter that converts a legacy XML API to work with a JSON-based system.
- Build a decorator system for a text editor that adds bold, italic, and underline features.
- Implement a computer facade that simplifies the startup process of a complex computer system.
- Create a logger decorator that adds logging functionality to any function.
- Build a media player adapter that can play different audio formats (MP3, WAV, FLAC).