Open In App

Flyweight Method Design Pattern in Java

Last Updated : 06 Dec, 2023
Improve
Improve
Like Article
Like
Save
Share
Report

A flyweight design pattern or flyweight method is defined as a structural pattern that is used to minimize memory usage or computational expenses by sharing as much as possible with other similar objects.

The key idea behind the Flyweight pattern is to use shared objects to support large numbers of fine-grained objects efficiently.

Key Component of Flyweight Design Patterns:

Flyweight Interface/Abstract Class: Define an interface or an abstract class that declares the methods that must be implemented by concrete flyweight objects. This interface usually represents the common functionality shared among the flyweight objects.

Java




public interface Flyweight {
    void operation();
}


Concrete Flyweight: Implement the Flyweight interface to create concrete flyweight objects. These objects are shared and can be used in multiple contexts.

Java




public class ConcreteFlyweight implements Flyweight {
    private String intrinsicState;
 
    public ConcreteFlyweight(String intrinsicState) {
        this.intrinsicState = intrinsicState;
    }
 
    @Override
    public void operation() {
        System.out.println("ConcreteFlyweight: " + intrinsicState);
    }
}


Flyweight Factory: Create a factory class that manages the creation and sharing of flyweight objects. The factory maintains a pool or cache of existing flyweight objects and returns them when requested.

Java




import java.util.HashMap;
import java.util.Map;
 
public class FlyweightFactory {
    private Map<String, Flyweight> flyweights = new HashMap<>();
 
    public Flyweight getFlyweight(String key) {
        if (!flyweights.containsKey(key)) {
            flyweights.put(key, new ConcreteFlyweight(key));
        }
        return flyweights.get(key);
    }
}


Client: The client is responsible for using the flyweight objects. It typically maintains the extrinsic state, which is unique to each instance.

Java




public class Client {
    public static void main(String[] args) {
        FlyweightFactory factory = new FlyweightFactory();
 
        Flyweight flyweight1 = factory.getFlyweight("shared");
        Flyweight flyweight2 = factory.getFlyweight("shared");
 
        flyweight1.operation();  // Output: ConcreteFlyweight: shared
        flyweight2.operation();  // Output: ConcreteFlyweight: shared
    }
}


In this pattern, the intrinsic state is shared among multiple instances, while the extrinsic state is passed as a parameter to the methods of the flyweight objects. This allows the flyweight objects to be reused in different contexts, reducing memory usage and improving performance.

Example of flyweight Design Patterns:

Problem Statement:

Here, we’ll create a ‘CoffeeOrder’ system where each order has intrinsic state (type of coffee) and extrinsic state (table number). so we will use Flyweight method pattern here to solve this case.

Overall Code for the above example:

Java




import java.util.HashMap;
import java.util.Map;
 
// Flyweight interface
interface CoffeeOrder {
    void serveCoffee(CoffeeOrderContext context);
}
 
// ConcreteFlyweight
class CoffeeFlavor implements CoffeeOrder {
    private final String flavor;
 
    public CoffeeFlavor(String flavor) {
        this.flavor = flavor;
    }
 
    @Override
    public void serveCoffee(CoffeeOrderContext context) {
        System.out.println("Serving coffee flavor " + flavor + " to table " + context.getTableNumber());
    }
}
 
// Context class to hold extrinsic state
class CoffeeOrderContext {
    private final int tableNumber;
 
    public CoffeeOrderContext(int tableNumber) {
        this.tableNumber = tableNumber;
    }
 
    public int getTableNumber() {
        return tableNumber;
    }
}
 
// Flyweight factory
class CoffeeOrderFactory {
    private final Map<String, CoffeeOrder> flavors = new HashMap<>();
 
    public CoffeeOrder getCoffeeOrder(String flavor) {
        CoffeeOrder coffeeOrder = flavors.get(flavor);
 
        if (coffeeOrder == null) {
            coffeeOrder = new CoffeeFlavor(flavor);
            flavors.put(flavor, coffeeOrder);
        }
 
        return coffeeOrder;
    }
}
 
// Client code
public class CoffeeShop {
    public static void main(String[] args) {
        CoffeeOrderFactory coffeeOrderFactory = new CoffeeOrderFactory();
 
        // Orders with intrinsic state (coffee flavor) and extrinsic state (table number)
        CoffeeOrder order1 = coffeeOrderFactory.getCoffeeOrder("Cappuccino");
        order1.serveCoffee(new CoffeeOrderContext(1));
 
        CoffeeOrder order2 = coffeeOrderFactory.getCoffeeOrder("Espresso");
        order2.serveCoffee(new CoffeeOrderContext(2));
 
        CoffeeOrder order3 = coffeeOrderFactory.getCoffeeOrder("Cappuccino");
        order3.serveCoffee(new CoffeeOrderContext(3));
 
        // The flavor "Cappuccino" is shared between order1 and order3
        // It reduces memory usage as the intrinsic state is shared among orders with the same flavor
    }
}


Output

Serving coffee flavor Cappuccino to table 1
Serving coffee flavor Espresso to table 2
Serving coffee flavor Cappuccino to table 3




Explanation of the above example:

In this example,

  • ‘CoffeeFlavor’ represents the concrete flyweight, and ‘CoffeeOrderContext’ represents the extrinsic state.
  • ‘CoffeeOrderFactory’ is responsible for managing and creating flyweights, ensuring that shared flyweights are reused.
  • The client code (‘CoffeeShop’) demonstrates how the flyweights are used to create and serve coffee orders with intrinsic and extrinsic states.

Diagrammatic Representation of Flyweight Design Pattern in Java

2023-11-24_11-14-06

Explanation

In the above figure:

  • Flyweight Interface: This interface declares a method through which flyweights can receive and act on extrinsic state.
  • ConcreteFlyweight: This class implements the Flyweight interface and represents concrete flyweights. These objects share state that is intrinsic to them.
  • UnsharedConcreteFlyweight: Some flyweight implementations may not be shared. In such cases, the unshared concrete flyweight is created.
  • FlyweightFactory: This class is responsible for creating and managing flyweight objects. It ensures that flyweights are shared and reused when appropriate.
  • Client: The client maintains a reference to flyweights and computes or stores the extrinsic state.

Use Case of Flyweight Design Patterns in Java

Here are some common use cases for the Flyweight pattern:

  • Graphics and Image Processing: In graphical applications, such as computer games or image processing software, the Flyweight pattern can be used for managing graphical elements like textures, sprites, or fonts. Similar graphics can share a common Flyweight object to save memory.
  • Database Connection Pooling: When dealing with database connections in applications, the Flyweight pattern can be applied to manage a pool of database connection objects. Instead of creating a new database connection every time one is needed, a shared connection from the pool can be reused, reducing the overhead of creating and closing connections frequently.
  • GUI Systems: Graphical User Interface (GUI) frameworks can benefit from the Flyweight pattern, especially when dealing with UI elements like buttons, icons, or tooltips. Commonly used UI elements can be shared among different parts of the application, reducing memory consumption.
  • Text Processing Applications: In applications that involve processing and analyzing large amounts of text, the Flyweight pattern can be applied to represent shared elements such as words, phrases, or linguistic constructs. This can lead to more efficient use of memory and improved performance.

By applying the Flyweight pattern in these scenarios, developers can achieve a balance between memory efficiency and performance, especially in situations where a large number of similar objects need to be managed.

Advantages of Flyweight Design Patterns in Java

Here are some advantages of using the Flyweight pattern:

  • Memory Efficiency: One of the primary advantages of the Flyweight pattern is its ability to reduce memory usage. It achieves this by sharing common parts of objects, rather than duplicating them across multiple instances.
  • Performance Improvement : By sharing common state across multiple objects, the Flyweight pattern can lead to performance improvements. This is particularly beneficial in situations where creating and managing a large number of objects would be computationally expensive.
  • Resource Conservation: The Flyweight pattern helps conserve resources by avoiding the redundant storage of shared data. Instead of each instance holding its own copy of shared data, they reference a shared instance of that data.
  • Object-Oriented Principle Compliance: The Flyweight pattern promotes the separation of intrinsic and extrinsic state. Intrinsic state is shared and stored in the flyweight objects, while extrinsic state is passed in when needed. This aligns with the object-oriented design principles of encapsulation and separation of concerns.
  • Improved Maintainability: Since the Flyweight pattern promotes the reuse of shared objects, changes to the shared state can be centralized and applied to all instances. This can make maintenance easier, as modifications are concentrated in a smaller set of objects.
  • Enhanced Scalability: The Flyweight pattern is particularly useful in scenarios where a large number of objects need to be created dynamically. It allows systems to scale more effectively by reducing the memory and processing overhead associated with creating and managing numerous objects.
  • Applicability in GUI and Graphics: The Flyweight pattern is often employed in graphical applications where numerous similar graphical elements need to be displayed. For example, in a drawing application, individual characters in a text document might be implemented as flyweight objects.

In summary, the Flyweight pattern is beneficial in situations where there is a need for a large number of similar objects, and memory or computational efficiency is a concern. It promotes reusability, reduces redundancy, and can lead to improved system performance.

Disadvantages of Flyweight Design Patterns in Java

While the Flyweight pattern offers several advantages, it also comes with certain disadvantages and considerations. Here are some potential drawbacks:

  • Increased Complexity: Introducing the Flyweight pattern may add complexity to the code, especially in scenarios where the separation of intrinsic and extrinsic state is not straightforward. This complexity can make the code harder to understand and maintain.
  • Potential for Overhead: In some cases, the overhead associated with managing and looking up shared flyweight objects may outweigh the benefits gained from memory savings. This is especially true when the number of instances is relatively small or when the cost of creating and managing the flyweight objects is high.
  • Limited Applicability: The Flyweight pattern is most effective when there is a significant amount of shared state among objects. In situations where objects are mostly unique and have little shared state, the benefits of the Flyweight pattern may be limited.
  • Thread Safety Concerns: If multiple threads are involved and modifications to shared flyweight objects are possible, additional synchronization mechanisms may be needed to ensure thread safety. This can introduce complexity and potentially impact performance.
  • Difficulty in Managing Extrinsic State: Managing the extrinsic state (state that is not shared) of flyweight objects can be challenging, especially if it involves complex interactions. Developers need to carefully manage the extrinsic state to avoid introducing errors or unexpected behavior.
  • Impact on Identity: The Flyweight pattern blurs the identity of objects by sharing their internal state. This can be problematic if the identity of individual objects is important in the application logic. For example, if two flyweight objects are considered equal based on their shared state, it might lead to unexpected behavior.
  • Design Rigidity: Introducing the Flyweight pattern may make the code more rigid, especially if the shared state needs to be modified or extended. Changes to the shared state may require modifications to multiple objects, potentially impacting the flexibility of the design.

It’s important to carefully consider the specific requirements and characteristics of a given problem before deciding to apply the Flyweight pattern. In some cases, the benefits may outweigh the drawbacks, while in others, the pattern may introduce unnecessary complexity.



Like Article
Suggest improvement
Previous
Next
Share your thoughts in the comments

Similar Reads