Latticework

Command Palette

Search for a command to run...

Design Patterns

Structural Patterns

18 min

Explanation

Structural patterns are about composing objects together — wrapping one object inside another to change or extend how it's used, without touching its original code. The Adapter pattern wraps an object whose interface doesn't match what you need, translating calls to match:

class OldRectangle:              # a class you can't (or don't want to) modify
    def __init__(self, w, h):
        self.w, self.h = w, h
    def compute_area(self):       # wrong method name for your codebase
        return self.w * self.h

class RectangleAdapter:
    def __init__(self, old_rect):
        self.old_rect = old_rect
    def area(self):               # the interface your code actually expects
        return self.old_rect.compute_area()
Try it

This is exactly how you'd integrate a third-party library (whose method names you can't change) into code written against your own project's interface conventions.

Loading editor…
Explanation

The Decorator pattern (a different thing from the @decorator syntax from the Python course, though conceptually related) wraps an object to ADD behavior, while keeping the SAME interface — callers can't tell the difference between the original object and the decorated one:

class LoggingShape:
    def __init__(self, shape):
        self.shape = shape
        self.call_count = 0

    def area(self):
        self.call_count += 1        # added behavior
        return self.shape.area()     # delegates to the wrapped object

logged = LoggingShape(Rectangle(3, 4))
print(logged.area())          # 12 -- works exactly like a normal shape
print(logged.call_count)       # 1 -- but now you can track usage

Unlike Adapter (which changes the interface), Decorator preserves it exactly — LoggingShape still has .area(), just like Rectangle does, so it can be used anywhere a Shape is expected.

Exercise

Given `OldRectangle` (with `compute_area()`, not `area()`) below, write `RectangleAdapter.area(self)` (the Adapter pattern): make the adapter expose the expected `.area()` interface by calling the wrapped object's `.compute_area()`.

Exercise

Write `LoggingShape.area(self)` (the Decorator pattern): wrap another shape, incrementing `self.call_count` every time `area()` is called, then return the wrapped shape's actual area.

Quiz

What's the key difference between the Adapter and Decorator patterns?

Checkpoint

You can use the Adapter pattern to translate a mismatched interface, and the Decorator pattern to add behavior around an object while keeping its interface identical.