Structural Patterns Part 2: Decorator, Proxy, Composite, and Flyweight
Understand patterns that add behavior dynamically, control access, model tree structures, and reduce memory overhead in repeated object graphs.
Inside this chapter
- Decorator Pattern
- Proxy Pattern
- Composite Pattern
- Flyweight Pattern
- Real-World Usage Snapshot
Series navigation
Study the chapters in order for the clearest path from first design principles to advanced Java architecture, framework usage, and interview-level pattern mastery. Use the navigation at the bottom of the page to move through the full tutorial smoothly.
Decorator Pattern
Decorator wraps an object to add responsibilities dynamically without modifying the original class. Java I/O classes are one of the classic examples of decoration. Decorator is often better than inheritance when behavior combinations may vary.
interface MessageService {
String send(String message);
}
class BasicMessageService implements MessageService {
public String send(String message) {
return message;
}
}
class EncryptionDecorator implements MessageService {
private final MessageService delegate;
EncryptionDecorator(MessageService delegate) {
this.delegate = delegate;
}
public String send(String message) {
return delegate.send("[encrypted]" + message);
}
} Proxy Pattern
Proxy controls access to another object. It may add lazy loading, access checks, logging, remote communication, caching, or transactional boundaries. In Java frameworks, generated proxies are everywhere.
Composite Pattern
Composite allows clients to treat individual objects and groups of objects uniformly. File systems, menu trees, organization hierarchies, and expression trees are classic examples.
interface FileSystemNode {
void print();
}
class FileLeaf implements FileSystemNode {
private final String name;
FileLeaf(String name) { this.name = name; }
public void print() { System.out.println(name); }
}
class DirectoryComposite implements FileSystemNode {
private final List<FileSystemNode> children = new ArrayList<>();
public void add(FileSystemNode node) { children.add(node); }
public void print() {
for (FileSystemNode child : children) {
child.print();
}
}
} Flyweight Pattern
Flyweight shares intrinsic state across many similar objects to reduce memory usage. It matters when systems create huge numbers of logically similar objects, such as text rendering, map tiles, or symbolic model nodes. Most business applications do not need manual flyweight logic often, but it is valuable in performance-sensitive systems.
Real-World Usage Snapshot
Java developers see decorator in stream wrappers, proxy in Spring AOP and ORM lazy loading, composite in tree models, and flyweight in optimized rendering or caching systems. These patterns become much clearer once you start recognizing them inside libraries you already use.