Decorator, Facade, and Flyweight Structural Patterns (GoF)
This page covers the implementation details and known uses of the Decorator pattern, then presents the complete Facade and Flyweight patterns. All three are structural patterns from the Gang of Four catalog. Decorator wraps objects to add responsibilities transparently; Facade simplifies access to complex subsystems; Flyweight uses sharing to efficiently support large numbers of fine-grained objects.
Key Concepts
Decorator:
Decorator holds a reference to a VisualComponent and forwards all operations to it by default
Concrete decorators (e.g., BorderDecorator, ScrollDecorator) override specific operations to add behavior, then call the parent's version
Decorators can be nested: new BorderDecorator(new ScrollDecorator(textView), 1) — order matters
The client (e.g., Window) interacts through the base VisualComponent interface and is unaware of decoration
Not limited to UI — also applies to I/O streams (e.g., CompressingStream, ASCII7Stream wrapping FileStream)
Frameworks like MacApp and Bedrock use decorator-like "behavior" objects for event handling — a view maintains a list of behaviors that can intercept and modify events
Facade:
Provides a unified, higher-level interface to a set of interfaces in a subsystem
The Facade knows which subsystem classes handle a request and delegates accordingly
Subsystem classes have no knowledge of the facade — no back-references
Three key benefits: (1) shields clients from subsystem components, (2) promotes weak coupling between subsystem and clients, (3) doesn't prevent direct access to subsystem classes when needed
Weak coupling reduces recompilation dependencies in large systems and simplifies porting
Can reduce client-subsystem coupling further by making Facade abstract with concrete subclasses for different implementations
Subsystems have public and private interfaces, analogous to class access control; the Facade is part of the public interface but not the only part
Usually only one Facade object is needed — often a Singleton
Flyweight:
Uses sharing to support large numbers of fine-grained objects without prohibitive memory cost
Intrinsic state: stored in the flyweight, independent of context, sharable (e.g., character code)
Extrinsic state: depends on context, cannot be shared, passed in by clients (e.g., position, font, color)
A FlyweightFactory creates and manages flyweight objects, ensuring proper sharing — clients must not instantiate ConcreteFlyweights directly
Not all flyweight subclasses need to be shared — UnsharedConcreteFlyweight (e.g., Row, Column) can have shared flyweights as children
Storage savings increase with: more sharing, more intrinsic state per object, computed (not stored) extrinsic state
Trade-off: run-time costs for transferring/computing extrinsic state vs. memory savings
When combined with Composite, shared leaf nodes cannot store parent pointers — parent must be passed as extrinsic state
Applicability conditions: large number of objects, high storage costs, most state can be made extrinsic, application doesn't depend on object identity
Commands and Syntax
Decorator composition (C++):
// Base component
class VisualComponent {
public:
virtual void Draw();
virtual void Resize();
};
// Abstract decorator — forwards all operations
class Decorator : public VisualComponent {
VisualComponent* _component;
public:
Decorator(VisualComponent*);
void Draw() { _component->Draw(); }
void Resize() { _component->Resize(); }
};
// Concrete decorator — adds border drawing
class BorderDecorator : public Decorator {
int _width;
void DrawBorder(int);
public:
BorderDecorator(VisualComponent*, int borderWidth);
void Draw() { Decorator::Draw(); DrawBorder(_width); }
};
// Composing decorators
window->SetContents(
new BorderDecorator(
new ScrollDecorator(textView), 1));
Decorator for streams:
Stream* aStream = new CompressingStream(
new ASCII7Stream(
new FileStream("aFileName")));
aStream->PutInt(12);
aStream->PutString("aString");
Adapter changes an object's interface; Decorator changes responsibilities but keeps the interface
Composite: Decorator is a degenerate composite with one component — not for aggregation
Strategy changes the "guts" (algorithm); Decorator changes the "skin" (added responsibilities)
Facade vs. related patterns:
Abstract Factory can be used with Facade to create subsystem objects in a subsystem-independent way
Mediator abstracts communication between colleague objects (colleagues know the mediator); Facade abstracts the interface to subsystem objects (subsystem doesn't know the facade)
Singleton: Facade objects are often Singletons
Builder: The compiler example uses Builder (ProgramNodeBuilder) internally
Composite: Parse tree (ProgramNode hierarchy) uses Composite
Visitor: CodeGenerator is a Visitor that traverses the parse tree
Strategy: MemoryObjectCache uses Strategy for caching policy
Flyweight vs. related patterns:
Often combined with Composite to represent hierarchical structures with shared leaf nodes
FlyweightFactory is analogous to an object pool or factory pattern — ensures sharing
Exam-Relevant Points
Decorator key rule: the decorator and the component share the same interface — clients cannot distinguish decorated from undecorated objects
Decorator ordering matters: BorderDecorator(ScrollDecorator(x)) is different from ScrollDecorator(BorderDecorator(x))
Decorator is not just for UI — I/O stream decoration (compression, encoding) is a classic non-UI example
Facade does NOT hide subsystem classes — clients can still access them directly when needed
Facade promotes weak coupling — subsystem classes have NO reference to the facade
Facade reduces compilation dependencies — changes in subsystem classes don't force recompilation of clients
Abstract Facade: making Facade abstract with concrete subclasses provides abstract coupling, hiding which subsystem implementation is used
Flyweight intrinsic vs. extrinsic state is the central concept — intrinsic is context-independent and shared; extrinsic is context-dependent and passed in
Flyweight identity warning: identity tests return true for conceptually distinct objects since they share the same instance — application must not depend on object identity
Clients must obtain flyweights from the FlyweightFactory, never instantiate directly
Flyweight + Composite constraint: shared leaf nodes cannot store parent pointers; parent must be passed as extrinsic state
UnsharedConcreteFlyweight is valid — the Flyweight interface enables sharing but doesn't enforce it
Flyweight applicability requires ALL conditions: large number of objects, high storage cost, most state can be made extrinsic, objects replaceable by few shared ones, no identity dependence