XREF Tech

$ less article.md

The Flyweight Pattern: Sharing Objects to Save Memory

The Flyweight Pattern is a structural Gang of Four pattern that saves RAM by sharing the parts of objects that do not change. Rather than allocating a full copy of repeated data for every object, an application keeps one immutable shared object and supplies each use with its changing context. For programs that create enormous numbers of fine-grained objects, that small shift matters.

Separate shared data from per-use data

At the core of the design, split an object’s state in two. Intrinsic state is constant, independent of context, and safe to share. Within the flyweight, it must be read-only to clients. Extrinsic state, by contrast, varies by use and is supplied when the flyweight performs an operation.

State type Where it lives Particle example
Intrinsic Inside the shared flyweight Color and sprite for a particle type
Extrinsic With the client or passed to an operation Coordinates, movement vector, and speed

When coordinates sit in a shared particle object, one particle’s movement becomes visible to every user of that object. With those coordinates outside it, a single immutable particle type can serve many independent particles.

See why sharing changes the memory equation

In a document, glyphs make the pattern tangible. Despite many occurrences of the letter A, a document does not need a separate object for its font data, outline, and metrics at every occurrence. For those shared properties, one glyph object is enough while document layout keeps each character’s position separately. At hundreds or thousands of bytes per character for a naive glyph object, sharing avoids duplicating a substantial immutable payload.

At a larger scale, the same shape appears in a game engine. In a forest containing 1,000,000 trees, a TreeType can retain a tree’s name, color, and texture. Per instance, only x/y coordinates and a reference to that type are needed in each Tree. Similarly, a particle system that once created complete objects for every bullet, rocket, and shrapnel fragment can reuse three particle types thousands of times while receiving position data externally.

Use a factory to make sharing invisible to clients

In a typical implementation, four participants cooperate: a flyweight interface that accepts extrinsic parameters, concrete flyweights holding intrinsic data, a factory that creates and caches them, and a client that requests them. For a key request, the factory either returns the existing object or creates, stores, and returns one. To the caller, the two cases look the same.

final class TreeType {
    private final String name;
    private final String color;
    private final String texture;

    TreeType(String name, String color, String texture) {
        this.name = name;
        this.color = color;
        this.texture = texture;
    }
}

final class TreeTypeFactory {
    private final Map<String, TreeType> cache = new HashMap<>();

    TreeType get(String name, String color, String texture) {
        String key = name + "|" + color + "|" + texture;
        return cache.computeIfAbsent(key,
            ignored -> new TreeType(name, color, texture));
    }
}

To distinguish shared objects correctly, the cache key must cover every intrinsic property. In this example, a request for the same name, color, and texture receives the same TreeType; a different texture requires a different one. Although a factory is often implemented as a Singleton to provide global access, the essential behavior is caching and reuse, not the access mechanism.

Keep the flyweight immutable and pass context at the call

For a flyweight, immutability is the contract that makes sharing safe. After receiving an object, the client must not mutate its intrinsic state. Instead, methods accept the information that differs for this particular use:

interface ParticleType {
    void draw(int x, int y, int speed);
}

At each call, the particle type can retain its color and sprite while x, y, and speed describe one rendering event. By keeping those values out of the object, the design prevents accidental sharing of changing state. In concurrent code, shared objects also require deliberate thread-safety design; mutable shared state invites race conditions.

Recognize flyweights already present in Java

Within the Java standard library, Integer.valueOf(int) is a direct example. From -128 through 127, cached Integer objects let repeated requests return an existing object rather than allocate another wrapper. The VM property java.lang.Integer.IntegerCache.high can raise the cache limit.

For equal string values, string interning applies the same sharing idea. Because strings are immutable, a string pool can store one copy of each distinct value. At compile time, Java automatically interns string constants, and String.intern() can use the pool explicitly. When equal values refer to the same pooled object, a pointer comparison is O(1) rather than a character-by-character O(n) comparison.

Elsewhere in the library, classes including Boolean, Byte, Character, Short, Long, and BigDecimal expose comparable valueOf() caching behavior. When browsing unfamiliar source code, a cached-object creation method such as valueOf() or getFlyweight() is a useful clue.

Choose Flyweight only after measuring a memory problem

As a memory optimization, Flyweight is not a default way to model every object. Its complexity is justified when an application creates a very large number of small objects, the total memory cost is prohibitive, and those objects have substantial shared data with only a few variations. Examples fitting that shape include text glyphs, map elements, reusable GUI components, and repeated game assets.

Before changing the design, start with a measurement and then inspect the object state. The following sequence keeps the refactoring focused:

  1. Confirm that RAM consumption is a demonstrated problem.
  2. Find the data repeated across many instances.
  3. Move only invariant, context-independent data into an immutable flyweight.
  4. Keep changing values with the client and pass them to operations.
  5. Use a factory cache keyed by the intrinsic state.
  6. Review concurrent access to the shared objects and factory.

Without a RAM constraint, the extra separation, cache management, and concurrency reasoning can cost more in complexity than it returns in savings.

Avoid the mistakes that turn sharing into coupling

Myth: a flyweight is simply any cache. Although a factory cache is part of the common implementation, the pattern’s defining move is separating shareable intrinsic state from changing extrinsic state.

Myth: all state can be kept in the shared object. By storing extrinsic state in the flyweight, a design removes the independence that made sharing possible and can create race conditions. Outside the shared object belong coordinates, movement, and other per-use values.

Myth: the pattern always makes an application faster. Reducing memory use is its stated purpose. While string interning can make some equality checks cheaper, Flyweight itself should be selected because measurement shows duplicated object data is a memory cost.

Frequently Asked Questions

What is the Flyweight Pattern?

As a structural GoF pattern, it reduces RAM use by sharing immutable, common object data and supplying changing context separately.

What is the difference between intrinsic and extrinsic state?

Because intrinsic state is constant and context-independent, the flyweight stores it. From one use to another, extrinsic state changes and is passed to the flyweight operation or kept by the client.

What does a flyweight factory do?

On the first request for an intrinsic-state key, it creates and caches a flyweight; for later matching requests, it returns the cached object.

Is Integer.valueOf() an example of Flyweight?

Yes. In the range from -128 to 127, Java caches Integer objects, allowing repeated requests to reuse an object.

When should a developer use Flyweight?

After measurement shows that a huge population of fine-grained objects consumes too much RAM, use it when many objects share substantial invariant data.

See also

  1. Facade Pattern: Simplifying Complex Subsystems
  2. The Singleton Pattern: How It Works, Code Examples and Why It Is Controversial
  3. Code Smells: Common Examples and How to Refactor Them
  4. Dead Code: How to Find and Safely Remove Unused Code

$ ls tools/

Xref-Speller Refactoring

Direct implementation of several refactorings is performed by others by using its source browsing capabilities. Refactoring, according to Martin Fowler, is a disciplined technique used to restructure codes.

XREF Program

In the current era, the word program does not only anchor its references to party organizations and business meetings, or even events.

Source Browsing

You might think that Xref-Speller illustrated various traces of change when it comes to web-surfing of hypertext files on the Internet.

Programs

The cross-reference file is generated after full pre-processing and parsing of sources (supporting both K&R and ANSI styles).