2.2.11Design Principles

Design Patterns — Structural - Adapter, Bridge, Composite, Decorator, Facade, Flyweight, Proxy

2,424 words11 min readdifficulty · medium

The seven classic structural patterns (GoF) split cleanly into two intentions:

Intention Patterns
Make incompatible / unrelated things work together Adapter, Bridge, Facade
Build / extend object trees and capabilities Composite, Decorator, Proxy, Flyweight
Figure — Design Patterns — Structural -  Adapter, Bridge, Composite, Decorator, Facade, Flyweight, Proxy

The 80/20: what actually matters


1. Adapter — translate an interface

WHY: You have a working class (a third-party library, legacy code) but its method names/signatures don't match what your code calls. You can't (or shouldn't) edit it.

WHAT: A wrapper exposing the target interface, internally calling the adaptee.

HOW (derive from scratch):

# Client wants this interface:
class MediaPlayer:           # Target
    def play(self, file): ...
 
# But we already have this incompatible class:
class LegacyMp4(object):     # Adaptee
    def start_mp4(self, path):
        print("playing", path)
 
# Bridge the gap by FORWARDING with a translated name:
class Mp4Adapter(MediaPlayer):
    def __init__(self, legacy):
        self._legacy = legacy        # composition: hold the adaptee
    def play(self, file):
        self._legacy.start_mp4(file) # translate play() -> start_mp4()

Why this step? Mp4Adapter implements the interface the client speaks (play), so the client never knows a legacy class exists.


2. Bridge — separate abstraction from implementation

HOW:

class Color:                      # Implementation side
    def fill(self): ...
class Red(Color):
    def fill(self): return "red"
 
class Shape:                      # Abstraction side
    def __init__(self, color):    # holds a reference -> the "bridge"
        self.color = color
    def draw(self): ...
class Circle(Shape):
    def draw(self):
        return "Circle in " + self.color.fill()

Why this step? Shape has-a Color (composition) instead of being a coloured shape (inheritance). Add a new colour → one class; add a new shape → one class.


3. Composite — treat one and many the same

WHY: Files & folders, GUI panels containing widgets, org charts — recursive part-whole hierarchies where you want render() or size() to work whether you call it on a leaf or a whole subtree.

HOW:

class Node:
    def size(self): raise NotImplementedError
class File(Node):                 # Leaf
    def __init__(self, kb): self.kb = kb
    def size(self): return self.kb
class Folder(Node):               # Composite
    def __init__(self): self.children = []
    def add(self, n): self.children.append(n)
    def size(self):
        return sum(c.size() for c in self.children)  # recurse uniformly

Why this step? Folder.size() calls size() on each child without checking its type — the recursion and the uniform interface are the whole point.


4. Decorator — add behaviour by wrapping

HOW:

class Coffee:                     # Component
    def cost(self): return 2.0
class AddOn(Coffee):              # Decorator base: wraps a Coffee, IS a Coffee
    def __init__(self, drink): self.drink = drink
class Milk(AddOn):
    def cost(self): return self.drink.cost() + 0.5   # forward + add
class Sugar(AddOn):
    def cost(self): return self.drink.cost() + 0.2
 
order = Sugar(Milk(Coffee()))     # stackable: 2.0 + 0.5 + 0.2 = 2.7

Why this step? Milk.cost() calls the wrapped object first, then adds its own bit — that "call inner, then extend" is what makes decorators composable.


5. Facade — one simple door to a complex subsystem

WHY: Starting a computer means CPU, Memory, Disk all cooperating. The client just wants start().

HOW:

class Facade:
    def __init__(self): self.cpu, self.mem, self.disk = CPU(), Mem(), Disk()
    def start(self):
        self.cpu.freeze(); self.mem.load(); self.disk.read(); self.cpu.jump()

Why this step? The facade orchestrates the subsystem so the client makes one call instead of four ordered ones. Note: Facade doesn't hide the subsystem — advanced clients can still reach in.


6. Flyweight — share to save memory

HOW:

class GlyphFactory:
    _pool = {}
    def get(self, char):
        if char not in self._pool:
            self._pool[char] = Glyph(char)   # create once, reuse forever
        return self._pool[char]              # SHARED instance
# extrinsic state (x,y) passed at draw time, not stored in Glyph

Why this step? The factory caches by intrinsic key so repeated requests return the same object — that sharing is the memory win.


7. Proxy — a stand-in that controls access

HOW:

class RealImage:
    def __init__(self, f): self._load(f)      # expensive!
    def display(self): ...
class ProxyImage:
    def __init__(self, f): self.f, self.real = f, None
    def display(self):
        if self.real is None:                 # lazy: load only on first use
            self.real = RealImage(self.f)
        self.real.display()

Why this step? The proxy defers the expensive construction until display() is actually called — same interface, controlled access.



Recall Feynman: explain to a 12-year-old

Imagine LEGO. Adapter is a little brick that connects two LEGO sets that normally don't click together. Decorator is putting stickers and lights on a car — each one adds something, and you can keep adding more. Composite is a box of LEGO that you can carry as one thing, even though it has tiny pieces inside, and boxes can hold boxes. Facade is one big red "GO" button that secretly does ten steps for you. Proxy is a guard who stands in front of a treasure room and only opens the door when you're really allowed in. Flyweight is using the same picture of a tree a thousand times in a video game instead of drawing a thousand trees. Bridge is keeping "shape" and "colour" as two separate sticker sheets so you mix any shape with any colour without making every combo by hand.


Flashcards

What problem does Adapter solve?
Two interfaces are incompatible; the adapter translates one into the other so they can work together.
Adapter vs Bridge — core difference?
Adapter fixes an existing mismatch after the fact; Bridge is designed up front to let abstraction & implementation vary independently (avoiding class explosion).
Why does Bridge reduce a class explosion?
It turns m×nm \times n combinations (inheritance) into m+nm + n classes (composition) by giving abstraction a reference to implementation.
Key property that lets Decorators stack?
A decorator implements the same interface as the object it wraps, so it's interchangeable and can be wrapped again.
Decorator vs Proxy — intent?
Decorator adds responsibilities; Proxy controls access (lazy load, permission, cache, remote).
What makes Composite powerful?
Leaf and group share one interface, so clients treat single objects and whole trees uniformly via recursion.
Does Facade hide the subsystem completely?
No — it simplifies access for common use, but clients can still reach the subsystem directly if needed.
Flyweight: intrinsic vs extrinsic state?
Intrinsic = shared, unchanging part stored in the shared object; extrinsic = context-specific part passed in from outside.
What are the four common Proxy types?
Virtual (lazy load), Protection (access control), Caching, Remote.
The single sentence unifying most structural patterns?
An object holds a reference to another object and forwards calls to it; patterns differ by intent (translate / extend / control / share / orchestrate).

Connections

  • Composition over Inheritance — the principle behind Adapter, Bridge, Decorator, Proxy
  • Open-Closed Principle — Decorator & Bridge extend behaviour without modifying code
  • Single Responsibility Principle — Facade isolates orchestration logic
  • Creational Patterns — Flyweight relies on a Factory to pool shared objects
  • Behavioral Patterns — Composite often pairs with Visitor / Iterator over the tree
  • Dependency Inversion — Bridge depends on the implementation interface, not concretes
  • Recursion — Composite's uniform size()/render() is recursive descent

Concept Map

translate interface

add behaviour, same interface

control access

one call to many subsystems

forward to children tree

two hierarchies

share heavy state

stackable wrappers

make incompatible things fit

Composition forwards calls

Adapter

Decorator

Proxy

Facade

Composite

Bridge

Flyweight

Hinglish (regional understanding)

Intuition Hinglish mein samjho

Structural patterns ka simple matlab hai: tumhare paas objects hain, ab unhe aapas me kaise jodna (wire karna) hai taaki pura system flexible rahe aur loosely coupled rahe. Yaad rakhne ka shortcut — zyadatar structural pattern me ek object dusre object ka reference rakhta hai aur calls ko forward kar deta hai (yeh hai "composition over inheritance"). Bas intent badalta hai: Adapter interface ko translate karta hai, Decorator behaviour add karta hai (aur stack ho sakta hai), Proxy access control karta hai (lazy load, permission), Facade kai subsystems ko ek simple call ke peeche chhupa deta hai.

Composite tree banata hai — File aur Folder dono ka same interface, isliye size() recursively kaam karta hai chahe ek file ho ya pura folder. Bridge bohot important hai interview ke liye: agar Shape × Color ko inheritance se banao to m×nm \times n classes ban jaati hain (RedCircle, BlueSquare...). Bridge ise m+nm + n kar deta hai kyunki Shape ke paas Color ka reference hota hai. Flyweight memory bachata hai — game me 1 million tree draw karne hain par sirf 3 alag mesh objects share karke.

Confusion points jo exam me aate hain: Adapter vs Bridge — Adapter purani incompatible cheez ko baad me fit karta hai; Bridge pehle se design hota hai taaki do dimensions independently vary karein. Decorator vs Proxy — dono wrap karte hain, par Decorator feature add karta hai, Proxy access control karta hai.

Go deeper — visual, from zero

Test yourself — Design Principles

Connections