1. Definition

The Flyweight Design Pattern involves sharing to support a large number of similar objects efficiently. It promotes the reuse of existing similar objects instead of creating new ones, thereby conserving resources.

2. Problem Statement

Imagine you are developing a graphic-intensive computer game. This game has thousands of trees, buildings, and characters. Creating a unique object for every tree or building would consume excessive memory, leading to performance issues.

3. Solution

The Flyweight Design Pattern allows you to share common parts of the object state among multiple objects, instead of keeping it in each one. The shared state is called the intrinsic state, and the rest (which might vary) is the extrinsic state. This way, objects become lighter (hence the name, Flyweight) and more memory-efficient.

4. Real-World Use Cases

1. Text editors or word processors which represent characters in the text.

2. Graphic editors that use patterns to fill shapes.

3. Games with extensive graphics where many entities share similar properties.

5. Implementation Steps

1. Divide the target object’s properties into intrinsic (shared) and extrinsic (external) states.

2. Create a Flyweight class containing the intrinsic state.

3. Ensure that the Flyweight class is immutable.

4. Create a factory that caches and reuses existing Flyweight class instances.

5. Clients should use the Flyweight factory to retrieve objects.

6. Implementation

// The flyweight class
public class TreeType {
    private String name;
    private String color;

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

    public void display(int x, int y) {
        System.out.println("Tree " + name + " with color " + color + " displayed at (" + x + "," + y + ")");
    }
}

// Factory to generate tree types
public class TreeFactory {
    private static Map<String, TreeType> treeTypes = new HashMap<>();

    public static TreeType getTreeType(String name, String color) {
        TreeType treeType = treeTypes.get(name);
        if (treeType == null) {
            treeType = new TreeType(name, color);
            treeTypes.put(name, treeType);
        }
        return treeType;
    }
}

// Client code
public class Forest {
    public static void main(String[] args) {
        TreeType oak = TreeFactory.getTreeType("Oak", "Green");
        oak.display(10, 20);

        TreeType pine = TreeFactory.getTreeType("Pine", "Dark Green");
        pine.display(30, 40);

        TreeType sameOak = TreeFactory.getTreeType("Oak", "Green");
        sameOak.display(50, 60);
    }
}

Output:

Tree Oak with color Green displayed at (10,20)
Tree Pine with color Dark Green displayed at (30,40)
Tree Oak with color Green displayed at (50,60)

Explanation:

Notice that even though we requested an "Oak" tree type twice, the Factory ensured that only one instance of the Oak tree type was created. This saved memory. The intrinsic state of the tree (its type and color) is shared, while the extrinsic state (its position) is provided by the client.

7. When to use?

1. Use the Flyweight pattern when you have a large number of similar objects causing a memory overhead.

2. When objects have shared states that can be moved to a shared flyweight object.

3. When the application doesn’t depend on object identity. Since flyweight objects are shared, identity checks will return true for shared objects.