Structural Design Patterns
Adapter

The Adapter Pattern is a structural design pattern that allows incompatible interfaces (Classes/Objects) to work together by converting one interface into another that a client expects.
When to Use
Integrating a legacy system or third-party library that doesn’t match your current interface.
Reusing existing functionality without modifying source code.
Bridging the gap between new and old systems with different interfaces.
The Problem – Payment Gateway Integration
You have a checkout system that expects all payment gateways to expose a pay(amount) method.
However, a third-party service like OldPay or LegacyPayService exposes a completely different interface:
The method name is
make_payment()Requires different arguments
Naive Implementation
Why Naivety Is a Problem
The interface (
make_payment) doesn’t match what the client (pay) expects.You can’t change
LegacyPayService(e.g., it’s third-party or legacy).No decoupling — tight integration breaks flexibility.
Challenges in Adapting: Interface mismatch, Closed source, Incompatible contracts.
Enter: Adapter Pattern
Two Types of Adapters:
Object Adapter
Uses composition – wraps the adaptee in a new object and translates interface calls
Class Adapter
Uses inheritance – adapter subclasses both adaptee and target (works in languages with multiple inheritance, like C++)
In Python, the Object Adapter is most commonly used.
Class Diagram

Client
Uses Target interface (pay(amount))
Target
Interface expected by the client
Adapter
Converts the interface of the Adaptee to match the Target
Adaptee
Existing class with a different interface (e.g., make_payment(value))
Code
What We Did and Achieved
Decoupled
Checkoutfrom specific payment gatewaysReused legacy code without modifying it
Bridged the mismatch between
pay()andmake_payment()Achieved interface compatibility with clean separation of concerns
Pros and Cons
Promotes reusability of legacy or third-party code
Can add extra layer of indirection
No need to modify existing code (adheres to OCP)
Requires one adapter per incompatible interface
Improves flexibility and testability
Adapter logic may become complex if APIs differ a lot
Great for plug-and-play architecture
Bridge

The Bridge Design Pattern is a structural pattern that decouples an abstraction from its implementation, so they can evolve independently.
"Bridge Pattern helps you avoid having a separate class for every combination of feature + platform by splitting them into two parts and connecting them via a bridge."
When to Use
When a class has multiple dimensions of variation (e.g., types of controls and types of devices).
When you want to avoid deep inheritance trees.
When implementation may change independently from the interface.
The Problem – Remote Controls and Devices
Suppose you have multiple devices (TV, Radio) and multiple remotes (BasicRemote, AdvancedRemote). Without bridge:
You’ll need: BasicRemoteForTV, AdvancedRemoteForTV, BasicRemoteForRadio, AdvancedRemoteForRadio → Combinatorial Explosion!
Naive Implementation
Why It’s a Problem
Code duplication for each device + remote pair
Inflexible and hard to maintain
Deep inheritance tree as variations grow
What We Really Need: A way to separate the remote control (abstraction) from the device (implementation) and allow combinations at runtime.
Enter: Bridge Pattern
The Bridge Pattern splits a class into two independent hierarchies:
Abstraction (e.g., Remote)
Implementation (e.g., Device)
...and connects them using composition, not inheritance.
Class Diagram

Abstraction
Interface that defines high-level operations (e.g., Remote)
Refined Abstraction
Extended version of Abstraction (e.g., AdvancedRemote)
Implementor
Interface for low-level operations (e.g., Device)
Concrete Implementor
Concrete classes implementing device behavior (e.g., TV, Radio)
Code
Output:
What We Achieved
Separated interface (Remote) from implementation (Device)
Easily extended on either side independently
No combinatorial explosion of classes
Flexible runtime composition
Pros and Cons
Decouples abstraction from implementation
Adds slight complexity via extra layers
Easy to extend or change parts independently
Requires good planning of abstraction levels
Avoids class explosion with multi-dimensional features
May be overkill for very simple scenarios
Good for platform-specific variations (e.g., UI, APIs)
Composite

The Composite Pattern is a structural design pattern that lets you treat individual objects and compositions of objects uniformly.
"It allows you to treat a single file and a folder of files the same way — so you don't need to write separate logic for one vs. many."
When to Use
When you need to represent part-whole hierarchies (e.g., folders and files, shapes inside groups).
When you want to treat individual and grouped objects uniformly.
To avoid complex branching logic in client code.
The Problem – File System
Let’s say you’re building a file explorer. Some files are individual files, others are folders containing files.
Why It’s a Problem
Client or composite (
Folder) has to manually checkisinstance()to treat leafs and composites differently.This violates Open/Closed Principle and is harder to extend.
Repeating logic across multiple composite nodes.
Enter: Composite Pattern
Instead of handling File and Folder separately, we define a common interface, and treat both as Components. This lets us invoke .open() on a File or a Folder without worrying which one it is.
Class Diagram

Client
Uses the component interface to interact with both files/folders
Component
Abstract interface (open()) common to both leaf and composite
Leaf
File – Implements open() directly
Composite
Folder – Stores children and delegates calls to them
Code
Output
What We Achieved
Uniform treatment of both files and folders
Clean and recursive design without
isinstance()checksEasy to add new node types (e.g., Symlink) by just implementing the
ComponentinterfaceAdheres to Open/Closed Principle
Pros and Cons
Treats single and grouped objects uniformly
Can be overkill for simple flat structures
Encourages clean recursive logic
Slightly more boilerplate due to interfaces
Simplifies client code
Needs careful design to prevent circular nesting
Easy to extend and refactor
Decorator

The Decorator Pattern is a structural pattern that lets you dynamically add new behavior or responsibilities to an object without modifying its structure or code. "It’s like gift-wrapping an object: you still have the same object inside, but you can add as many layers (decorators) around it as you like—each adding new functionality."
When to Use
To extend the functionality of a class without subclassing it
To compose behaviors at runtime in different combinations
To avoid bloated classes with multiple
if/elseflags for optional features
The Problem – Social Notifications
You have a system that sends notifications to users. Sometimes they get only email, other times email + SMS, or all platforms (email + Slack + Instagram...).
Why It's a Problem
Hardcoded logic makes it non-extensible
Adding/removing platforms requires changing the core class
Violates Open/Closed Principle
Combinations require multiple flags or subclasses
Enter: Decorator Pattern
We define a core component (Notifier) and then decorate it dynamically at runtime with new responsibilities (SMSDecorator, SlackDecorator, etc.).
Class Diagram

Client
Uses the Notifier interface
Component
Common interface for all notifiers (send(message))
Concrete Component
Base notifier (e.g., EmailNotifier)
Decorator (abstract)
Holds reference to a Notifier; implements same interface
Concrete Decorators
Add functionality like SMS, Slack, etc. around the base notifier
Code
Output
What We Achieved
Added new behaviors without modifying base class
Flexible and composable - decorators can be stacked dynamically
Adheres to Open/Closed Principle
Clean and scalable for adding new channels
Pros and Cons
Add behavior without changing existing code
Many small classes can clutter the codebase
Flexible combinations of functionality
Debugging layered decorators may be harder
Follows Open/Closed Principle
Execution order can affect results if not managed clearly
Can be added or removed at runtime
Adds runtime overhead with multiple wrappers
Facade

Flyweight
Proxy
References
Last updated