Flyweight Pattern

The Flyweight pattern is a structural design pattern used to minimize memory usage and enhance performance by sharing as much data as possible with similar objects. It is particularly useful when dealing with a large number of similar objects that can benefit from sharing common data, thus reducing the overall memory footprint. The Flyweight pattern achieves this by separating intrinsic (shared) state from extrinsic (non-shared) state and managing them efficiently.

Important Components

  1. Flyweight: An interface or abstract class defining the methods for handling intrinsic and extrinsic state.
  2. Concrete Flyweight: Implements the Flyweight interface, storing intrinsic state and processing extrinsic state passed by the client.
  3. Flyweight Factory: Manages a pool of Flyweight objects, ensuring shared instances are reused and creating new ones only when necessary.
  4. Client: Uses the Flyweight Factory to obtain Flyweight objects and passes extrinsic state to them as needed.

How It Works

  • Intrinsic State: Data that is immutable and shared across multiple objects (e.g., a character’s font or a tree’s type).
  • Extrinsic State: Data that is unique to a specific context and passed to the Flyweight (e.g., a character’s position or a tree’s coordinates).
  • The Flyweight Factory maintains a pool of Flyweight objects, keyed by their intrinsic state.
  • The client requests a Flyweight from the factory, which returns an existing instance or creates a new one if none exists.
  • The client provides the extrinsic state when invoking the Flyweight’s methods.

Example in Java

Let’s model a forest simulation where trees share common properties (type, color) as intrinsic state, but have unique positions (x, y coordinates) as extrinsic state.

import java.util.HashMap;
import java.util.Map;

// Flyweight Interface
interface Tree {
    void display(int x, int y);
}

// Concrete Flyweight
class TreeType implements Tree {
    private String type; // Intrinsic state
    private String color;

    public TreeType(String type, String color) {
        this.type = type;
        this.color = color;
    }

    @Override
    public void display(int x, int y) { // Extrinsic state (x, y)
        System.out.println("Displaying " + color + " " + type + " tree at (" + x + ", " + y + ")");
    }
}

// Flyweight Factory
class TreeFactory {
    private static Map<String, TreeType> treeTypes = new HashMap<>();

    public static TreeType getTreeType(String type, String color) {
        String key = type + "-" + color;
        TreeType treeType = treeTypes.get(key);
        if (treeType == null) {
            treeType = new TreeType(type, color);
            treeTypes.put(key, treeType);
            System.out.println("Created new TreeType: " + type + ", " + color);
        }
        return treeType;
    }
}

// Client
class Forest {
    private static class TreePosition {
        Tree tree;
        int x, y;

        TreePosition(Tree tree, int x, int y) {
            this.tree = tree;
            this.x = x;
            this.y = y;
        }
    }

    private List<TreePosition> trees = new ArrayList<>();

    public void plantTree(int x, int y, String type, String color) {
        Tree tree = TreeFactory.getTreeType(type, color);
        trees.add(new TreePosition(tree, x, y));
    }

    public void display() {
        for (TreePosition treePos : trees) {
            treePos.tree.display(treePos.x, treePos.y);
        }
    }
}

// Usage
public class Main {
    public static void main(String[] args) {
        Forest forest = new Forest();

        // Plant trees with shared intrinsic state
        forest.plantTree(1, 1, "Oak", "Green");
        forest.plantTree(2, 2, "Oak", "Green"); // Reuses same TreeType
        forest.plantTree(3, 3, "Pine", "DarkGreen");
        forest.plantTree(4, 4, "Oak", "Green"); // Reuses same TreeType

        // Display all trees
        forest.display();
    }
}
/*
Created new TreeType: Oak, Green
Created new TreeType: Pine, DarkGreen
Displaying Green Oak tree at (1, 1)
Displaying Green Oak tree at (2, 2)
Displaying DarkGreen Pine tree at (3, 3)
Displaying Green Oak tree at (4, 4)
*/Code language: JavaScript (javascript)

Use case and Implementation

Managing a large number of similar transactions or account states efficiently by sharing common data among them.

//FlyWeightDemo.java
import java.util.HashMap;
import java.util.Map;

//Flyweight Interface
interface TransactionFlyweight {
    void processTransaction(String accountNumber, double amount, String timestamp);
}
//Concrete Flyweight Class
class Transaction implements TransactionFlyweight {
    private final String transactionType;
    private final String status;

    public Transaction(String transactionType, String status) {
        this.transactionType = transactionType;
        this.status = status;
    }

    @Override
    public void processTransaction(String accountNumber, double amount, String timestamp) {
        System.out.println("Processing " + transactionType + " transaction for account: " + accountNumber +
                           ", amount: $" + amount + ", at: " + timestamp + ", status: " + status);
    }
}
//Flyweight Factory Class:

class TransactionFactory {
    private final Map<String, TransactionFlyweight> flyweights = new HashMap<>();

    public TransactionFlyweight getFlyweight(String transactionType, String status) {
        String key = transactionType + status;
        if (!flyweights.containsKey(key)) {
            flyweights.put(key, new Transaction(transactionType, status));
        }
        return flyweights.get(key);
    }

    public int getTotalFlyweights() {
        return flyweights.size();
    }
}
//Client Class
class TransactionManager {
    private final TransactionFactory transactionFactory;

    public TransactionManager(TransactionFactory transactionFactory) {
        this.transactionFactory = transactionFactory;
    }

    public void executeTransaction(String accountNumber, double amount, String timestamp, String transactionType, String status) {
        TransactionFlyweight flyweight = transactionFactory.getFlyweight(transactionType, status);
        flyweight.processTransaction(accountNumber, amount, timestamp);
    }
}
class FlyWeightDemo{
	public static void main(String[] args) {
		TransactionFactory factory = new TransactionFactory();
		TransactionManager manager = new TransactionManager(factory);
		manager.executeTransaction("123456", 1000.00, "2024-07-09T10:00:00", "Deposit", "Completed");
		manager.executeTransaction("123457", 500.00, "2024-07-09T10:05:00", "Withdrawal", "Pending");
		manager.executeTransaction("123456", 200.00, "2024-07-09T10:10:00", "Deposit", "Completed");
		manager.executeTransaction("123458", 1500.00, "2024-07-09T10:15:00", "Deposit", "Pending");
		System.out.println("Total Flyweights created: " + factory.getTotalFlyweights());
    }
}

/*
C:\>javac FlyWeightDemo.java

C:\>java FlyWeightDemo
Processing Deposit transaction for account: 123456, amount: $1000.0, at: 2024-07-09T10:00:00, status: Completed
Processing Withdrawal transaction for account: 123457, amount: $500.0, at: 2024-07-09T10:05:00, status: Pending
Processing Deposit transaction for account: 123456, amount: $200.0, at: 2024-07-09T10:10:00, status: Completed
Processing Deposit transaction for account: 123458, amount: $1500.0, at: 2024-07-09T10:15:00, status: Pending
Total Flyweights created: 3

Pros

  • Memory Efficiency: Reduces memory usage by sharing common state across multiple objects.
  • Scalability: Enables handling a large number of objects without significant resource overhead.
  • Maintainability: Centralizes intrinsic state management in the Flyweight Factory.

Cons

  • Complexity: Introduces additional classes and logic for managing intrinsic and extrinsic state.
  • Trade-Offs: May increase runtime overhead due to passing extrinsic state repeatedly.
  • Thread Safety: Shared Flyweight objects must be immutable or thread-safe to avoid concurrency issues.

When to Use

  • When you need to create a large number of similar objects that share common state.
  • When memory usage is a concern, and you can separate intrinsic and extrinsic state.
  • When the intrinsic state can be shared without affecting object behavior.
  • When the application benefits from reusing objects instead of creating new ones.

Real-World Example

  • Text Editors: Storing character properties (font, size) as Flyweights, with position as extrinsic state (e.g., Java’s Glyph in text rendering).
  • Game Development: Rendering thousands of similar objects (e.g., trees, bullets) with shared models but unique positions.
  • Graphics Systems: Managing reusable textures or shapes with varying transformations.

The Flyweight Pattern is a structural design pattern that focuses on efficiently sharing objects to minimize memory usage and improve performance, particularly in applications that generate a large number of similar objects. In Java, it’s especially beneficial in systems like text editors, graphic editors, or games where many objects share common, immutable state.

By separating intrinsic (shared) from extrinsic (context-specific) data and reusing existing instances, the Flyweight Pattern enables significant resource savings and system optimization. It exemplifies object pooling and data deduplication principles, reducing memory footprint and improving scalability.

Scroll to Top