Structural Patterns
24 min read
Overview
Structural patterns are concerned with how classes and objects are composed to form larger structures. They divide into two families that exploit two different language mechanisms. Structural class patterns use inheritance to compose interfaces or implementations — as a simple example, multiple inheritance mixes two or more classes into one, producing a class that combines the properties of its parents, which is particularly useful for making independently developed class libraries work together. Structural object patterns instead describe ways to compose objects to realize new functionality. The added flexibility of object composition comes from the ability to change the composition at run-time, which is impossible with static class composition.
This topic catalogs the seven structural patterns. Adapter makes one interface conform to another. Bridge separates an abstraction from its implementation so the two can vary independently. Composite builds part-whole hierarchies in which clients treat individual objects and compositions uniformly. Decorator adds responsibilities to objects dynamically by recursive composition. Facade makes a single object represent an entire subsystem. Flyweight defines a structure for sharing fine-grained objects efficiently. Proxy acts as a surrogate or placeholder that controls access to another object. Many of these patterns are related — they rely on the same small set of structuring mechanisms — but each carries a distinct intent, and the closing discussion contrasts the look-alike pairs (Adapter vs. Bridge; Composite vs. Decorator vs. Proxy) so the differences in intent stay clear.
Key takeaways
Mental model
Adapter
Class and object structural pattern. Also known as Wrapper.
Intent
Convert the interface of a class into another interface clients expect. Adapter lets classes work together that couldn't otherwise because of incompatible interfaces.
Motivation
Sometimes a toolkit class that's designed for reuse isn't reusable only because its interface doesn't match the domain-specific interface an application requires. Consider a drawing editor whose key abstraction is the graphical object: an abstract class Shape with one subclass per kind of object (LineShape, PolygonShape, and so on). A TextShape subclass that can display and edit text is hard to implement, but an off-the-shelf toolkit might already provide a sophisticated TextView class — which the editor cannot use directly because the toolkit wasn't designed with Shape in mind.
Changing TextView to conform to Shape isn't an option without the toolkit's source, and even with it the toolkit shouldn't have to adopt domain-specific interfaces. Instead, define TextShape so it adapts the TextView interface to Shape's, in one of two ways: (1) by inheriting Shape's interface and TextView's implementation, or (2) by composing a TextView instance within a TextShape and implementing TextShape in terms of TextView's interface. These two approaches are the class and object versions of the Adapter pattern. The adapter is also responsible for functionality the adapted class doesn't provide — for instance, TextShape implements Shape's CreateManipulator operation (returning a TextManipulator) to add the dragging behavior TextView lacks but Shape requires.
Applicability
Use the Adapter pattern when:
- you want to use an existing class, and its interface does not match the one you need.
- you want to create a reusable class that cooperates with unrelated or unforeseen classes — classes that don't necessarily have compatible interfaces.
- (object adapter only) you need to use several existing subclasses, but it's impractical to adapt their interface by subclassing every one. An object adapter can adapt the interface of its parent class.
Structure and Participants
A class adapter uses multiple inheritance to adapt one interface to another; an object adapter relies on object composition.
- Target (Shape) — defines the domain-specific interface that Client uses.
- Client (DrawingEditor) — collaborates with objects conforming to the Target interface.
- Adaptee (TextView) — defines an existing interface that needs adapting.
- Adapter (TextShape) — adapts the interface of Adaptee to the Target interface.
Collaboration: clients call operations on an Adapter instance; in turn, the adapter calls Adaptee operations that carry out the request.
Consequences
Class and object adapters have different trade-offs. A class adapter:
- adapts Adaptee to Target by committing to a concrete Adaptee class — so it won't work when you want to adapt a class and all its subclasses.
- lets Adapter override some of Adaptee's behavior, since Adapter is a subclass of Adaptee.
- introduces only one object, with no additional pointer indirection needed to reach the adaptee.
An object adapter:
- lets a single Adapter work with many Adaptees — the Adaptee itself and all its subclasses — and can add functionality to all Adaptees at once.
- makes it harder to override Adaptee behavior (you must subclass Adaptee and make Adapter refer to the subclass).
Other issues: how much adapting an adapter does ranges from simple name changes to supporting an entirely different operation set. Pluggable adapters build interface adaptation into a class so other classes need not assume a single interface (for example, a reusable TreeDisplay widget that can display both directory and inheritance hierarchies). Two-way adapters provide transparency when two clients need to view an object differently — for example, a ConstraintStateVariable that subclasses both Unidraw's StateVariable and QOCA's ConstraintVariable.
Implementation and Sample Code
In C++, a class adapter inherits publicly from Target and privately from Adaptee; thus Adapter is a subtype of Target but not of Adaptee. The key is to use one inheritance branch for the interface and another for the implementation. Pluggable adapters first identify a narrow interface for the adaptee — the smallest subset of operations that lets you do the adaptation — then build adaptation in three ways: abstract operations, delegate objects, or parameterized blocks.
The class-adapter form of TextShape (public Shape, private TextView):
class TextShape : public Shape, private TextView {
public:
TextShape();
// convert TextView's interface to Shape's
virtual void BoundingBox(Point& bottomLeft, Point& topRight) const {
Coord bottom, left, width, height;
GetOrigin(bottom, left); // inherited from TextView
GetExtent(width, height); // inherited from TextView
bottomLeft = Point(bottom, left);
topRight = Point(bottom + height, left + width);
}
// direct forwarding of a request
virtual bool IsEmpty() const {
return TextView::IsEmpty();
}
// functionality TextView lacks, defined from scratch
virtual Manipulator* CreateManipulator() const {
return new TextManipulator(this);
}
};
The object-adapter form keeps a pointer to a TextView and forwards through it; CreateManipulator is unchanged because it is implemented from scratch and reuses nothing from TextView:
class TextShape : public Shape {
public:
TextShape(TextView*);
virtual void BoundingBox(Point& bottomLeft, Point& topRight) const {
Coord bottom, left, width, height;
_text->GetOrigin(bottom, left);
_text->GetExtent(width, height);
bottomLeft = Point(bottom, left);
topRight = Point(bottom + height, left + width);
}
virtual bool IsEmpty() const { return _text->IsEmpty(); }
virtual Manipulator* CreateManipulator() const {
return new TextManipulator(this);
}
private:
TextView* _text;
};
The object adapter takes a little more effort but is more flexible: it works equally well with subclasses of TextView — the client simply passes an instance of a TextView subclass to the constructor.
Related Patterns
Bridge has a structure similar to an object adapter but a different intent: separating an interface from its implementation so they can vary independently, rather than changing the interface of an existing object. Decorator enhances another object without changing its interface, and so is more transparent than an adapter and supports recursive composition. Proxy defines a representative for another object and does not change its interface.
Bridge
Object structural pattern. Also known as Handle/Body.
Intent
Decouple an abstraction from its implementation so that the two can vary independently.
Motivation
When an abstraction can have one of several implementations, the usual accommodation is inheritance: an abstract class defines the interface, and concrete subclasses implement it differently. But inheritance binds an implementation to the abstraction permanently, making it hard to modify, extend, and reuse the two independently. Consider a portable Window abstraction that must run on both the X Window System and IBM's Presentation Manager. Subclassing Window into XWindow and PMWindow has two drawbacks: extending Window (say, an IconWindow) forces a new class per platform (XIconWindow, PMIconWindow, and so on), and client code becomes platform-dependent because creating an XWindow binds the abstraction to the X implementation.
The Bridge pattern addresses this by putting the Window abstraction and its implementation in separate class hierarchies: one for window interfaces (Window, IconWindow, TransientWindow) and a separate hierarchy rooted at WindowImp for platform-specific implementations (XWindowImp, PMWindowImp). All operations on Window subclasses are implemented in terms of abstract operations from the WindowImp interface. The relationship between Window and WindowImp is the bridge.
Applicability
Use the Bridge pattern when:
- you want to avoid a permanent binding between an abstraction and its implementation (for example, when the implementation must be selected or switched at run-time).
- both the abstractions and their implementations should be extensible by subclassing independently.
- changes in the implementation of an abstraction should have no impact on clients — their code should not have to be recompiled.
- (C++) you want to hide the implementation of an abstraction completely from clients.
- you have a proliferation of classes — what Rumbaugh calls "nested generalizations."
- you want to share an implementation among multiple objects (perhaps using reference counting) and hide that fact from the client (for example, Coplien's String class sharing a StringRep).
Structure and Participants
- Abstraction (Window) — defines the abstraction's interface and maintains a reference to an Implementor object.
- RefinedAbstraction (IconWindow) — extends the interface defined by Abstraction.
- Implementor (WindowImp) — defines the interface for implementation classes; it need not match Abstraction's interface and is typically only primitive operations, with Abstraction defining higher-level operations on top of them.
- ConcreteImplementor (XWindowImp, PMWindowImp) — implements the Implementor interface.
Collaboration: Abstraction forwards client requests to its Implementor object.
Consequences
- Decoupling interface and implementation. An implementation is not bound permanently to an interface; it can be configured (even changed) at run-time. Decoupling also eliminates compile-time dependencies — changing an implementation class doesn't require recompiling the Abstraction class and its clients, which is essential for binary compatibility across library versions. It encourages layering.
- Improved extensibility. You can extend the Abstraction and Implementor hierarchies independently.
- Hiding implementation details from clients — including the sharing of implementor objects and any reference-count mechanism.
Implementation and Sample Code
When there's only one implementor, an abstract Implementor class isn't strictly necessary (a degenerate Bridge), but the separation is still useful so clients need only relink, not recompile — Carolan's "Cheshire Cat." To choose the right Implementor, Abstraction can instantiate one in its constructor based on parameters, pick a default and switch later by usage, or delegate the decision to an Abstract Factory. Note that combining interface and implementation with C++ multiple inheritance binds them statically, so it cannot implement a true Bridge.
Window keeps a reference to a WindowImp and defines its operations in terms of WindowImp primitives:
class Window {
public:
Window(View* contents);
// requests handled by the window abstraction
virtual void DrawContents();
virtual void Open();
virtual void Close();
virtual void Iconify();
// requests forwarded to the implementation
virtual void DrawRect(const Point& p1, const Point& p2) {
WindowImp* imp = GetWindowImp();
imp->DeviceRect(p1.X(), p1.Y(), p2.X(), p2.Y());
}
protected:
WindowImp* GetWindowImp(); // lazily fetched from the factory
View* GetView();
private:
WindowImp* _imp;
View* _contents;
};
Concrete WindowImp subclasses implement the primitives in terms of a specific window system:
class XWindowImp : public WindowImp {
public:
XWindowImp();
virtual void DeviceRect(Coord, Coord, Coord, Coord);
// ... other X-specific operations
private:
// lots of X Window System-specific state, including:
Display* _dpy;
Drawable _winid; // window id
GC _gc; // graphic context
};
void XWindowImp::DeviceRect(Coord x0, Coord y0, Coord x1, Coord y1) {
int x = round(min(x0, x1));
int y = round(min(y0, y1));
int w = round(abs(x0 - x1));
int h = round(abs(y0 - y1));
XDrawRectangle(_dpy, _winid, _gc, x, y, w, h);
}
A Window obtains the right WindowImp from an Abstract Factory (made a Singleton for simplicity) that encapsulates all window-system specifics.
Related Patterns
An Abstract Factory can create and configure a particular Bridge. The Adapter pattern is geared toward making unrelated classes work together and is usually applied after systems are designed; Bridge is used up-front to let abstractions and implementations vary independently.
Composite
Object structural pattern.
Intent
Compose objects into tree structures to represent part-whole hierarchies. Composite lets clients treat individual objects and compositions of objects uniformly.
Motivation
Graphics applications let users group simple components into larger components, recursively. A naive implementation defines classes for primitives (Text, Line) plus separate container classes — but then client code must treat primitive and container objects differently, even though users treat them identically. The key to Composite is an abstract class (Graphic) that represents both primitives and their containers. Graphic declares operations like Draw plus child-management operations. Primitive subclasses (Line, Rectangle, Text) implement Draw and ignore child operations; the Picture class implements Draw by calling Draw on its children and implements the child operations — and because Picture conforms to Graphic, Pictures compose other Pictures recursively.
Applicability
Use the Composite pattern when:
- you want to represent part-whole hierarchies of objects.
- you want clients to be able to ignore the difference between compositions of objects and individual objects, treating all objects in the composite structure uniformly.
Structure and Participants
- Component (Graphic) — declares the interface for objects in the composition, implements default behavior common to all classes, and declares an interface for accessing and managing child components (and, optionally, the parent).
- Leaf (Rectangle, Line, Text) — represents leaf objects; has no children; defines behavior for primitive objects.
- Composite (Picture) — defines behavior for components having children, stores child components, and implements the child-related operations.
- Client — manipulates objects in the composition through the Component interface.
Collaboration: clients use the Component interface. If the recipient is a Leaf, the request is handled directly; if it's a Composite, it usually forwards to its children, possibly performing additional work before or after forwarding.
Consequences
The Composite pattern:
- defines class hierarchies of primitive and composite objects, composable recursively; wherever client code expects a primitive, it can also take a composite.
- makes the client simple — clients treat composite structures and individual objects uniformly, avoiding tag-and-case-statement functions over the composition classes.
- makes it easier to add new kinds of components — new Composite or Leaf subclasses work with existing structures and client code.
- can make your design overly general: it's harder to restrict the components of a composite, and you can't rely on the type system for that — you must use run-time checks.
Implementation
Several issues recur. Explicit parent references simplify traversal and deletion and support Chain of Responsibility; keep the invariant that each child's parent is the composite that has it as a child by changing parent only in Add/Remove. Sharing components is hard when a component has at most one parent — Flyweight shows how to externalize state to avoid storing parents. Maximizing the Component interface means defining as many common operations as possible, even providing defaults (for example, a default child-access operation that returns no children). Declaring child-management operations is a trade-off between safety and transparency: declaring Add/Remove in Component gives transparency but lets clients add to leaves; declaring them only in Composite gives compile-time safety but different interfaces. The book emphasizes transparency, making Add/Remove fail by default on leaves. Other issues: whether Component stores the child list (a space penalty for every leaf), child ordering (use Iterator), caching traversal/search results, who deletes children (usually the Composite), and which data structure stores children.
class Component {
public:
virtual ~Component();
virtual void Add(Component*);
virtual void Remove(Component*);
virtual Component* GetChild(int);
// transparency-over-safety query: returns null by default
virtual Composite* GetComposite() { return 0; }
};
class Composite : public Component {
public:
virtual Composite* GetComposite() { return this; }
virtual void Add(Component*);
virtual void Remove(Component*);
virtual Component* GetChild(int);
private:
List<Component*> _children;
};
Sample Code
Equipment (computers and stereo components) organizes naturally as a part-whole hierarchy. Equipment is the Component; CompositeEquipment is the Composite base for equipment that contains other equipment:
class Equipment {
public:
virtual ~Equipment();
const char* Name() { return _name; }
virtual Watt Power();
virtual Currency NetPrice();
virtual Currency DiscountPrice();
virtual void Add(Equipment*);
virtual void Remove(Equipment*);
virtual Iterator<Equipment*>* CreateIterator();
protected:
Equipment(const char*);
private:
const char* _name;
};
class CompositeEquipment : public Equipment {
public:
virtual ~CompositeEquipment();
virtual Currency NetPrice(); // sum over children
virtual void Add(Equipment*);
virtual void Remove(Equipment*);
virtual Iterator<Equipment*>* CreateIterator();
protected:
CompositeEquipment(const char*);
private:
List<Equipment*> _equipment;
};
Currency CompositeEquipment::NetPrice() {
Iterator<Equipment*>* i = CreateIterator();
Currency total = 0;
for (i->First(); !i->IsDone(); i->Next()) {
total += i->CurrentItem()->NetPrice();
}
delete i;
return total;
}
Concrete containers such as Chassis, Cabinet, and Bus subclass CompositeEquipment and inherit the child-related operations, letting you assemble a whole personal computer from nested parts.
Related Patterns
The component-parent link is often used for a Chain of Responsibility. Decorator is often used with Composite (they then share a common parent class, so decorators support Add/Remove/GetChild). Flyweight lets you share components but then they cannot refer to parents. Iterator can traverse composites. Visitor localizes operations that would otherwise be spread across Composite and Leaf.
Decorator
Object structural pattern. Also known as Wrapper.
Intent
Attach additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality.
Motivation
Sometimes you want to add responsibilities to individual objects, not to a whole class — for instance, adding a border or scrolling to a particular user-interface component. Adding them by inheritance is inflexible because the choice is static; a client can't control how and when to decorate. A more flexible approach encloses the component in another object — the decorator — that conforms to the component's interface so its presence is transparent to clients. The decorator forwards requests to the component and may perform additional actions before or after forwarding. Because it's transparent, you can nest decorators recursively for an unlimited number of added responsibilities: wrap a TextView in a ScrollDecorator for scroll bars and a BorderDecorator for a border.
Applicability
Use Decorator:
- to add responsibilities to individual objects dynamically and transparently, without affecting other objects.
- for responsibilities that can be withdrawn.
- when extension by subclassing is impractical — when many independent extensions would produce an explosion of subclasses, or when a class definition is hidden or otherwise unavailable for subclassing.
Structure and Participants
- Component (VisualComponent) — defines the interface for objects that can have responsibilities added dynamically.
- ConcreteComponent (TextView) — defines an object to which responsibilities can be attached.
- Decorator — maintains a reference to a Component object and defines an interface conforming to Component's.
- ConcreteDecorator (BorderDecorator, ScrollDecorator) — adds responsibilities to the component.
Collaboration: Decorator forwards requests to its Component object, optionally performing additional operations before and after.
Consequences
The Decorator pattern has two key benefits and two liabilities:
- More flexibility than static inheritance. Responsibilities can be added and removed at run-time by attaching and detaching decorators; you can mix and match, and even add a property twice (a double border by attaching two BorderDecorators).
- Avoids feature-laden classes high in the hierarchy. A pay-as-you-go approach: define a simple class and add functionality incrementally, so an application needn't pay for features it doesn't use.
- A decorator and its component aren't identical. From an object-identity standpoint a decorated component is not the component itself, so don't rely on object identity.
- Lots of little objects. Designs that use Decorator often produce many similar small objects that differ only in how they're interconnected — easy to customize, but hard to learn and debug.
Implementation
Issues to consider: a decorator's interface must conform to its component's, so ConcreteDecorators inherit from a common class (at least in C++). You can omit the abstract Decorator class when adding only one responsibility, folding the forwarding into the ConcreteDecorator. Keep the Component class lightweight — focused on interface, not data storage — so decorators stay cheap enough to use in quantity. Finally, Decorator changes an object's skin; the Strategy pattern changes its guts and is a better choice when the Component class is intrinsically heavyweight, because a strategy can have its own lightweight interface.
Sample Code
The Decorator keeps a reference to a VisualComponent and forwards every operation by default; concrete decorators override the operations they extend:
class Decorator : public VisualComponent {
public:
Decorator(VisualComponent*);
virtual void Draw() { _component->Draw(); }
virtual void Resize() { _component->Resize(); }
// ... forward the rest of VisualComponent's interface
private:
VisualComponent* _component;
};
class BorderDecorator : public Decorator {
public:
BorderDecorator(VisualComponent*, int borderWidth);
virtual void Draw() {
Decorator::Draw(); // forward, then add the decoration
DrawBorder(_width);
}
private:
void DrawBorder(int);
int _width;
};
Composing decorators to build a bordered, scrollable text view, then placing it into a window through the VisualComponent interface (so the Window is unaware of the decorators):
void Window::SetContents(VisualComponent* contents); // assumed
VisualComponent* textView = new TextView;
window->SetContents(
new BorderDecorator(
new ScrollDecorator(textView), 1
)
);
The same technique applies beyond GUIs — for example, a Stream hierarchy where a StreamDecorator's subclasses (CompressingStream, ASCII7Stream) override HandleBufferFull to compress or re-encode data before forwarding to a FileStream.
Related Patterns
A decorator differs from an Adapter: a decorator changes an object's responsibilities, not its interface; an adapter gives an object a completely new interface. A decorator can be viewed as a degenerate Composite with only one component, but it adds responsibilities rather than aggregating objects. Decorator changes the skin of an object; Strategy changes the guts.
Facade
Object structural pattern.
Intent
Provide a unified interface to a set of interfaces in a subsystem. Facade defines a higher-level interface that makes the subsystem easier to use.
Motivation
Structuring a system into subsystems reduces complexity, and a common goal is to minimize communication and dependencies between subsystems. A facade object provides a single, simplified interface to a subsystem's more general facilities. Consider a programming environment that gives applications access to its compiler subsystem (Scanner, Parser, ProgramNode, BytecodeStream, ProgramNodeBuilder). Most clients just want to compile some code; the low-level interfaces only complicate their task. A Compiler class acts as a facade — offering a single, simple interface that glues the implementing classes together without hiding them completely, so the few clients that need lower-level access still have it.
Applicability
Use the Facade pattern when:
- you want to provide a simple interface to a complex subsystem — a default view good enough for most clients, with the rest free to look beyond the facade.
- there are many dependencies between clients and the implementation classes of an abstraction; a facade decouples the subsystem from clients and other subsystems.
- you want to layer your subsystems — use a facade as the entry point to each level, so dependent subsystems communicate only through their facades.
Structure and Participants
- Facade (Compiler) — knows which subsystem classes are responsible for a request and delegates client requests to the appropriate subsystem objects.
- subsystem classes (Scanner, Parser, ProgramNode, and so on) — implement subsystem functionality, handle work assigned by the Facade, and keep no reference to it.
Collaboration: clients communicate with the subsystem by sending requests to Facade, which forwards them to the appropriate subsystem objects; clients that use the facade don't access subsystem objects directly.
Consequences
The Facade pattern:
- shields clients from subsystem components, reducing the number of objects clients deal with and making the subsystem easier to use.
- promotes weak coupling between subsystem and clients, letting you vary subsystem components without affecting clients, and eliminating complex or circular dependencies. Reducing compilation dependencies is vital in large systems — a facade limits recompilation when subsystem classes change and simplifies porting.
- doesn't prevent applications from using subsystem classes if they need to — you choose between ease of use and generality.
Implementation and Sample Code
Coupling can be reduced further by making Facade an abstract class with concrete subclasses per implementation, so clients don't know which implementation is used; alternatively, configure a Facade object with different subsystem objects. On public versus private subsystem classes: a subsystem is like a class with public and private interfaces — the Facade is part of the public interface, but other subsystem classes (Parser, Scanner) are usually public too. Few languages support private classes, though C++ namespaces let you expose just the public subsystem classes.
The Compiler facade wraps the whole subsystem behind one operation:
class Compiler {
public:
Compiler();
virtual void Compile(istream&, BytecodeStream& output);
};
void Compiler::Compile(istream& input, BytecodeStream& output) {
Scanner scanner(input);
ProgramNodeBuilder builder;
Parser parser;
parser.Parse(scanner, builder);
RISCCodeGenerator generator(output);
ProgramNode* parseTree = builder.GetRootNode();
parseTree->Traverse(generator);
}
This hard-codes the code-generator type; if more than one target architecture is needed, the constructor could take a CodeGenerator parameter — at the cost of detracting from the facade's mission of simplifying the common case.
Related Patterns
Abstract Factory can be used with Facade to create subsystem objects in a subsystem-independent way, or as an alternative to hide platform-specific classes. Mediator is similar in that it abstracts existing classes' functionality, but Mediator centralizes communication between colleague objects that know about it, whereas a facade merely simplifies the interface and subsystem classes don't know about it. Facade objects are often Singletons.
Flyweight
Object structural pattern.
Intent
Use sharing to support large numbers of fine-grained objects efficiently.
Motivation
Some applications would benefit from using objects throughout, but a naive implementation would be prohibitively expensive. A document editor could represent each character as an object — gaining uniform treatment of characters and embedded elements — but even moderate documents may need hundreds of thousands of character objects, consuming too much memory and run-time overhead. The Flyweight pattern shares objects so they can be used at fine granularities without prohibitive cost.
A flyweight is a shared object usable in multiple contexts simultaneously, indistinguishable from an unshared instance, that makes no assumptions about its context. The key concept is the distinction between intrinsic and extrinsic state. Intrinsic state is stored in the flyweight and is independent of context, making it sharable; extrinsic state depends on and varies with context and therefore can't be shared — client objects pass it to the flyweight when needed. A document editor can create one flyweight per letter of the alphabet: each stores a character code (intrinsic), while position and typographic style (extrinsic) come from the layout algorithm and formatting commands in effect wherever the character appears. A document of any length then allocates on the order of 100 character objects.
Applicability
Apply the Flyweight pattern when all of the following are true:
- an application uses a large number of objects.
- storage costs are high because of the sheer quantity of objects.
- most object state can be made extrinsic.
- many groups of objects can be replaced by relatively few shared objects once extrinsic state is removed.
- the application doesn't depend on object identity (shared flyweights will return true for identity tests on conceptually distinct objects).
Structure and Participants
- Flyweight (Glyph) — declares an interface through which flyweights can receive and act on extrinsic state.
- ConcreteFlyweight (Character) — implements the Flyweight interface and adds storage for intrinsic state; must be sharable, and any state it stores must be intrinsic.
- UnsharedConcreteFlyweight (Row, Column) — not all Flyweight subclasses need to be shared; the interface enables sharing without enforcing it. These often have ConcreteFlyweight objects as children.
- FlyweightFactory — creates and manages flyweight objects and ensures they're shared properly: on request, it supplies an existing instance or creates one.
- Client — maintains references to flyweights and computes or stores their extrinsic state.
Collaboration: state must be characterized as intrinsic (stored in the ConcreteFlyweight) or extrinsic (stored or computed by Client and passed in). Clients should obtain ConcreteFlyweights exclusively from the FlyweightFactory, never instantiate them directly.
Consequences
Flyweights may introduce run-time costs to transfer, find, or compute extrinsic state — offset by space savings that grow as more flyweights are shared. Storage savings are a function of the reduction in total instances, the amount of intrinsic state per object, and whether extrinsic state is computed or stored. The greatest savings occur when objects use substantial intrinsic and extrinsic state and the extrinsic state can be computed rather than stored. Flyweight is often combined with Composite to represent a hierarchical structure as a graph with shared leaf nodes; a consequence is that flyweight leaf nodes can't store a parent pointer — the parent is passed as extrinsic state.
Implementation and Sample Code
Two implementation issues dominate. Removing extrinsic state is what makes the pattern worthwhile — ideally extrinsic state can be computed from a separate, much smaller object structure (for example, a map of typographic runs rather than a font stored per character). Managing shared objects means clients look flyweights up through a FlyweightFactory (often an associative store keyed by, say, character code), with reference counting or garbage collection unless the flyweight set is fixed and small.
A Glyph base class with a Character ConcreteFlyweight that stores only the character code, and a GlyphFactory that shares Character instances:
class Glyph {
public:
virtual ~Glyph();
virtual void Draw(Window*, GlyphContext&);
// font passed in via GlyphContext (extrinsic state)
virtual void SetFont(Font*, GlyphContext&);
virtual Font* GetFont(GlyphContext&);
// child operations update the context during traversal
virtual void First(GlyphContext&);
virtual void Next(GlyphContext&);
virtual Glyph* Current(GlyphContext&);
};
class Character : public Glyph {
public:
Character(char c) : _charcode(c) { }
virtual void Draw(Window*, GlyphContext&);
private:
char _charcode; // intrinsic state only
};
const int NCHARCODES = 128;
class GlyphFactory {
public:
GlyphFactory() {
for (int i = 0; i < NCHARCODES; ++i) _character[i] = 0;
}
virtual Character* CreateCharacter(char c) {
if (!_character[c]) {
_character[c] = new Character(c); // create once, then share
}
return _character[c];
}
virtual Row* CreateRow(); // unshared
virtual Column* CreateColumn(); // unshared
private:
Character* _character[NCHARCODES];
};
Extrinsic font state lives in a GlyphContext, which keeps a compact mapping (a BTree keyed by glyph index) between a glyph and its font in different contexts; GlyphContext::Next advances the index during traversal, and GetFont descends the BTree to find the font for the current index. Because font changes are infrequent, the tree stays small relative to the glyph structure.
Related Patterns
Flyweight is often combined with Composite to implement a logically hierarchical structure as a directed-acyclic graph with shared leaf nodes. It's often best to implement State and Strategy objects as flyweights.
Proxy
Object structural pattern. Also known as Surrogate.
Intent
Provide a surrogate or placeholder for another object to control access to it.
Motivation
One reason to control access to an object is to defer the full cost of its creation and initialization until it's actually needed. A document editor that embeds graphical objects shouldn't create all the expensive objects (like large raster images) when a document opens, since not all will be visible at once. The solution is an image proxy that stands in for the real image, creating it only when the editor asks it to display itself (its Draw operation), and forwarding subsequent requests directly to the image. The proxy stores the image's file name (its reference) and its extent — letting it answer size requests from the formatter without instantiating the image.
Applicability
Proxy is applicable whenever there's a need for a more versatile or sophisticated reference than a simple pointer. Common situations:
- A remote proxy provides a local representative for an object in a different address space.
- A virtual proxy creates expensive objects on demand (the ImageProxy above).
- A protection proxy controls access to the original object when objects should have different access rights.
- A smart reference replaces a bare pointer and performs additional actions on access — reference counting (smart pointers), loading a persistent object on first reference, or checking that the real object is locked before access.
Structure and Participants
- Proxy (ImageProxy) — maintains a reference that lets it access the real subject; provides an interface identical to Subject's so it can be substituted for the real subject; controls access to the real subject and may create and delete it. Other responsibilities depend on the kind of proxy (remote proxies encode and forward requests; virtual proxies may cache information such as the real image's extent; protection proxies check access permissions).
- Subject (Graphic) — defines the common interface for RealSubject and Proxy so a Proxy can be used wherever a RealSubject is expected.
- RealSubject (Image) — defines the real object the proxy represents.
Collaboration: Proxy forwards requests to RealSubject when appropriate, depending on the kind of proxy.
Consequences
The Proxy pattern introduces a level of indirection when accessing an object, with uses depending on the kind of proxy: a remote proxy hides that an object lives in a different address space; a virtual proxy can create objects on demand; protection proxies and smart references allow housekeeping on access. The indirection also enables copy-on-write: copying a large object is deferred by using a proxy and reference counting, so the object is copied only when a client actually modifies it — significantly reducing the cost of copying heavyweight subjects.
Implementation and Sample Code
Proxy can exploit language features. In C++, overloading the member-access operator lets a proxy do extra work whenever it's dereferenced, behaving like a pointer — but this doesn't work when the proxy must know which operation is called (the image must load on Draw specifically, not on every reference), in which case each operation is forwarded manually. In Smalltalk, redefining the doesNotUnderstand: hook forwards trapped messages automatically. A Proxy doesn't always have to know the concrete RealSubject type — only when it must instantiate the subject (as a virtual proxy does).
A virtual proxy with the same interface as Image, caching the extent and loading lazily:
class Graphic {
public:
virtual ~Graphic();
virtual void Draw(const Point& at) = 0;
virtual void HandleMouse(Event& event) = 0;
virtual const Point& GetExtent() = 0;
virtual void Load(istream& from) = 0;
virtual void Save(ostream& to) = 0;
};
class ImageProxy : public Graphic {
public:
ImageProxy(const char* fileName);
virtual const Point& GetExtent() {
// return cached extent without instantiating the image
if (_extent == Point::Zero) {
_extent = GetImage()->GetExtent();
}
return _extent;
}
virtual void Draw(const Point& at) { GetImage()->Draw(at); }
virtual void HandleMouse(Event& e) { GetImage()->HandleMouse(e); }
virtual void Save(ostream& to); // save extent + file name
virtual void Load(istream& from);
protected:
Image* GetImage() {
if (_image == 0) { _image = new Image(_fileName); }
return _image;
}
private:
Image* _image;
Point _extent;
char* _fileName;
};
Related Patterns
An Adapter provides a different interface to the object it adapts; a Proxy provides the same interface (though a protection proxy's interface may effectively be a subset). Decorator implementations can resemble proxies, but a decorator adds responsibilities while a proxy controls access; a protection proxy may be implemented exactly like a decorator, whereas remote and virtual proxies hold indirect references (a host address or a file name) rather than a direct reference.
Discussion of Structural Patterns
The structural patterns resemble one another in participants and collaborations because they rely on the same small set of mechanisms — single and multiple inheritance for class-based patterns, object composition for object patterns. But the similarities belie different intents.
Adapter versus Bridge
Both promote flexibility by providing a level of indirection to another object, and both forward requests from an interface other than the object's own. The key difference is intent. Adapter focuses on resolving incompatibilities between two existing interfaces — making two independently designed classes work together without reimplementing either. Bridge bridges an abstraction and its (potentially numerous) implementations, providing a stable interface to clients while letting the implementing classes vary and accommodating new implementations as the system evolves. As a result they're used at different points in the lifecycle: an adapter is needed after you discover two incompatible classes should cooperate (the coupling is unforeseen), whereas a bridge is used up-front when you know an abstraction must have several implementations. Adapter makes things work after they're designed; Bridge makes them work before. A facade may look like an adapter to a set of objects, but a facade defines a new interface, whereas an adapter reuses an old one.
Composite versus Decorator versus Proxy
Composite and Decorator have similar structure diagrams because both rely on recursive composition to organize an open-ended number of objects — but the similarity ends there. Decorator adds responsibilities without subclassing, avoiding a subclass explosion; Composite structures classes so many related objects can be treated uniformly and multiple objects treated as one. The intents are distinct but complementary, so the two are often used together: from Decorator's view a composite is a ConcreteComponent, and from Composite's view a decorator is a Leaf.
Proxy also resembles Decorator — both provide indirection and keep a reference to another object they forward to — but Proxy is not concerned with attaching or detaching properties dynamically and is not designed for recursive composition. Its intent is to provide a stand-in when accessing the subject directly is inconvenient or undesirable (remote, restricted, or persistent). In Proxy, the subject defines the key functionality and the proxy provides or refuses access; in Decorator, the component provides only part of the functionality and decorators furnish the rest — and that open-endedness makes recursive composition essential to Decorator but not to Proxy.
Related lessons
Related concepts
- Compositionlinked concept
- Decouplinglinked concept
- Abstractionlinked concept
- Object-Oriented Designlinked concept