Use sharing to support large numbers of fine-grained objects efficiently.
The Flyweight pattern's effectiveness depends heavily on how and where it's used. 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 may be replaced by relatively few shared objects once extrinsic state is removed
- The application doesn't depend on object identity. Since flyweight objects may be shared, identity tests will return true for conceptually distinct objects
The following diagram show how flyweights are shared:
Flyweight: declares an interface through which flyweights can receive and act on extrinsic stateConcreteFlyweight: implements theFlyweightinterface and adds storage for intrinsic state, if any. AConcreteFlyweightobject must be sharable. Any state it stores must be intrinsic; that is, it must be independent of theConcreteFlyweightobject's context.UnsharedConcreteFlyweight: not allFlyweightsubclasses need to be shared. TheFlyweightinterface enables sharing; it doesn't enforce it. It's common forUnsharedConcreteFlyweightobjects to haveConcreteFlyweightobjects as children at some level in the flyweight object structureFlyweightFactory:- creates and manages flyweight objects
- ensures that flyweights are shared properly. When a client requests a flyweight, the
FlyweightFactoryobject supplies an existing instance or creates one, if none exists
Client:- maintains a reference to flyweight
- computes or stores the extrinsic state of flyweight(s)
- State that a flyweight needs to function must be characterized as either intrinsic or extrinsic. Intrinsic state is stored in the
ConcreteFlyweightobject; extrinsic state is stored or computed byClientobjects.Clients pass this state to the flyweight when they invoke its operations. - Clients should not instantiate
ConcreteFlyweightsdirectly. Clients must obtainConcreteFlyweightobjects exclusively from theFlyweightFactoryobject to ensure they are shared properly.
Flyweights may introduce run-time costs associated with transferring, finding, and/or computing extrinsic state, especially if it was formerly stored as intrinsic state. However, such costs are offset by space savings, which increase as more flyweights are shared.
Storage savings are a function of several factors:
- the reduction in the total number of instances that comes from sharing
- the amount of intrinsic state per object
- whether extrinsic state is computed or stored
The Flyweight pattern is often combined with the Composite pattern to represent a hierarchical structure as a graph with shared leaf nodes. A consequence of sharing is that flyweight leaf nodes cannot store a pointer to their parent. Rather, the parent pointer is passed to the flyweight as part of its extrinsic state. This has a major impact on how the objects in the hierarchy communicate with each other.
The Flyweight pattern is often combined with the Composite pattern to implement a logically hierarchical structure in terms of a directed-acyclic graph with shared leaf nodes.
It's often best to implement State and Strategy objects as flyweights.
interface Context {
}
interface Glyph {
void draw(Context context);
}
class Character implements Glyph {
private final char charCode; // intrinsic state
Character(char charCode) {
this.charCode = charCode;
}
@Override
public void draw(Context context) {
// do drawing
}
}
class Column implements Glyph {
private final int number; // intrinsic state
Column(int number) {
this.number = number;
}
@Override
public void draw(Context context) {
// do drawing
}
}
class Row implements Glyph {
private final int number;
Row(int number) {
this.number = number;
}
@Override
public void draw(Context context) {
// do drawing
}
}
final class GlyphFactory {
private static final Map<java.lang.Character, Glyph> characters = new HashMap<>();
private static final Map<Integer, Glyph> columns = new HashMap<>();
private static final Map<Integer, Glyph> rows = new HashMap<>();
static Glyph getCharacter(char fromChar) {
// reuse existing character object or create it if absent
return characters.computeIfAbsent(fromChar, ignored -> new Character(fromChar));
}
static Glyph getColumn(int forColumnNumber) {
// reuse existing column object or create it if absent
return columns.computeIfAbsent(forColumnNumber, ignored -> new Column(forColumnNumber));
}
static Glyph getRow(int forRowNumber) {
// reuse existing row object or create it if absent
return rows.computeIfAbsent(forRowNumber, ignored -> new Row(forRowNumber));
}
}
// --
public class Document {
public static void main(String[] args) {
Glyph documentBody = GlyphFactory.getColumn(0);
Glyph firstRow = GlyphFactory.getRow(0);
Glyph A = GlyphFactory.getCharacter('A');
Glyph B = GlyphFactory.getCharacter('B');
Glyph B2 = GlyphFactory.getCharacter('B');
Glyph A2 = GlyphFactory.getCharacter('A');
documentBody.draw(getContextForGlyph(documentBody));
firstRow.draw(getContextForGlyph(firstRow)); // a document with a text:
A.draw(getContextForGlyph(A)); // A
B.draw(getContextForGlyph(B)); // B
B2.draw(getContextForGlyph(B2)); // B -- this is a shared object (from the previous B creation)
A2.draw(getContextForGlyph(A2)); // A -- this is a shared object (from the previous A creation)
}
// some auxiliary calculation of the context for each glyph
// containing the extrinsic state
static Context getContextForGlyph(Glyph glyph) {
return new Context() {};
}
}

