devnotes.

Facade and Composite Patterns

Facade provides a simplified, unified interface to a complex subsystem made of many interacting classes, so client code doesn't need to understand or coordinate all those parts directly.

// A complex subsystem with many interacting parts
class CPU { function freeze() {} function jump(pos) {} function execute() {} }
class Memory { function load(pos, data) {} }
class HardDrive { function read(sector, size) {} }

// The facade hides all that complexity behind one simple method
class ComputerFacade {
    private cpu = new CPU()
    private memory = new Memory()
    private hardDrive = new HardDrive()

    function start() {
        this.cpu.freeze()
        this.memory.load(0, this.hardDrive.read(0, 1024))
        this.cpu.jump(0)
        this.cpu.execute()
    }
}

computer = new ComputerFacade()
computer.start() // caller doesn't need to know about CPU, Memory, or HardDrive at all

This is one of the most common patterns in everyday application code — a UserService or OrderService class is often, in essence, a facade over many lower-level repositories, validators, and external API clients.

Composite lets you treat individual objects and groups of objects uniformly, through a shared interface — ideal for representing tree-like, part-whole hierarchies such as file systems (files and folders), UI components (a button vs. a panel containing many buttons), or organization charts.

interface FileSystemItem {
    function getSize(): number
}

class File implements FileSystemItem {
    function __construct(private size) {}
    function getSize(): number { return this.size }
}

class Folder implements FileSystemItem {
    private items = []

    function add(FileSystemItem item) { this.items.push(item) }

    function getSize(): number {
        total = 0
        for item in this.items:
            total += item.getSize() // works the same whether item is a File or another Folder
        return total
    }
}

root = new Folder()
root.add(new File(100))
subfolder = new Folder()
subfolder.add(new File(200))
subfolder.add(new File(50))
root.add(subfolder)

print(root.getSize()) // 350 — calculated recursively, callers never distinguish File from Folder

The key insight: both File and Folder implement the same FileSystemItem interface, so code that calls getSize() never needs to check 'is this a leaf or a container?' — the recursive structure and shared interface handle that transparently, another instance of polymorphism at work.

Facade and Composite Design Patterns Explained | DevNotes