Flyweight Design Pattern

The Flyweight Design Pattern is used when we want to create large number of similar object. Instead of creating large numbers of similar objects, it reused the similar object created earlier. Creating large number of objects consumes memory resources and reduce the performance of your code, flyweight design pattern solves this problem by sharing similar objects.


In the flyweight pattern, segregating object properties into Intrinsic and Extrinsic state. Intrinsic states are things that are constant and stored inside of the objects. Extrinsic states are things that are the differences in objects and need to be calculated on the fly, they are moved outside of the objects and placed in client code. The object with intrinsic state is called flyweight object.

Flyweight design pattern comes under structural pattern as it provides ways to reduce number of objects and optimize performance. In this tutorial, we will explore the Flyweight Design Pattern in Java, discussing its structure, implementation, best practices, and advantages.


Structure of Flyweight Design Pattern

The Flyweight Design Pattern involves the following key components:
  • Flyweight Interface : This is the interface that declares methods common to all concrete flyweights. It may include methods for setting or getting intrinsic state.

  • Concrete Flyweight : This is the concrete implementation of the flyweight interface. It contains the intrinsic state that is shared among multiple objects.

  • Unshared Concrete Flyweight : In some cases, not all flyweights can be shared. The unshared concrete flyweight represents objects with unshareable intrinsic state.

  • Flyweight Factory: : This is responsible for managing flyweight objects and ensuring that they are shared properly. It may include methods for creating or retrieving flyweights.

  • Client : The client is responsible for using flyweights. It maintains the extrinsic state, which is not shared among flyweights.

Advantages of Flyweight Design Pattern

  • Memory Usage Optimization : The primary advantage of the Flyweight pattern is the optimization of memory usage. By sharing common intrinsic state among multiple objects, the pattern reduces the overall memory footprint.

  • Enhanced Scalability : The Flyweight pattern enhances scalability by allowing a large number of flyweights to be efficiently managed and shared. This is particularly advantageous in systems that need to handle a dynamic and potentially large number of objects.

  • Improved Performance : The sharing of flyweights leads to improved performance, as the creation and maintenance costs of shared objects are significantly lower than those of distinct objects. This is especially beneficial in scenarios with a large number of similar objects.

  • Simplified Object Creation : The pattern simplifies the creation of objects by centralizing the responsibility in a factory. This centralization ensures consistent creation processes and allows for the reuse of shared flyweights.

  • Flexible and Extensible : The Flyweight pattern is flexible and extensible. New flyweights can be added to the system without modifying existing ones. This flexibility is particularly useful in evolving systems with changing requirements.

  • Reduction of Redundant State : Shared flyweights help in reducing redundant state across multiple objects. The intrinsic state is maintained in a shared manner, minimizing duplication and ensuring consistency.

  • Promotion of Reusability : By encouraging the reuse of shared flyweights, the pattern promotes reusability of existing objects. This reusability contributes to a more modular and maintainable codebase.

When we should use Flyweight Pattern

  • When we want to create large number of similar objects having almost similar functionality. Instead of creating large number of instances of a class we can reuse already existing similar objects by storing them. IF no match found, then we will create a new object and store it for future reuse.
  • When we want to reduce the storage cost of large number of objects and improve performance.
  • When we can make object specific attributes external and can be computes on run-time.
  • When client doesn't enforce unique objects.

Let's take a real world example of ticket booking system. This system issues large number of tickets to passengers per day. Tickets can be of two types "ADULT" and "INFANT". In ticket booking system, ticket objects doesn't have much functionalities except printing itself. Passenger name and fare can vary depending on the ticket type. We can use flyweight design pattern in this scenario as every ticket of a particular type(let's say adult) is almost similar except it's extrinsic state(Name and fare) which can be passed to ticket object on runtime.


Implementation of Flyweight Design Pattern

We will define Ticket interface which contains the public method to set extrinsic properties(name and fare) of the ticket and printTicket method to enforce common ticket printing functionality.

Flyweight Design Pattern UML Diagram Ticket.java
public interface Ticket {
    public void setName(String name);
    public void setFare(int fare);
    public void printTicket();
}

RailwayTicket is a concrete implementation of Ticket interface.

public class RailwayTicket implements Ticket {
    private String type;
    private int fare;
    private String name;
 
    public RailwayTicket(String type){
        this.type = type;
    }
 
    public void setName(String name){
        this.name = name;
    }
 
    public void setFare(int fare){
        this.fare = fare;
    }
 
    @Override
    public void printTicket(){
        System.out.println("--------TICKET--------");
        System.out.println("Name : "+ name + "\nTicket Type : " 
            + type + "\nFare : " + fare);
    }
}

TicketFactory is a factory class to provide Railway tickets of different types. It stores one instance of various ticket types in a HashMap having ticket type as key. It exposes a 'getTicket' method to clients, which calls this method by passing the railway ticket fields. Inside this method, it first checks whether we already have an instance of requested ticket type. If present, it reuses it and sets the extrinsic attributes of tickets and returns it to client(TicketBookingSystem) other wise it creates a new instance of ticket and stores it in HashMap for future use.

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

public class TicketFactory {
    private static Map<String, Ticket> ticketMap 
        = new HashMap<String, Ticket>();

    public static Ticket getTicket(String type, String name, int fare){
        Ticket ticket;
        if(ticketMap.containsKey(type)){
            ticket = ticketMap.get(type);
        } else {
            ticket = new RailwayTicket(type);
            ticketMap.put(type, ticket);
        }
  
        ticket.setName(name);
        ticket.setFare(fare);
  
        return ticket;
    } 
}

TicketBookingSystem is the client of TicketFactory, which created large number of tickets on run time by calling getTicket method.

TicketBookingSystem.java
import java.io.BufferedReader;  
import java.io.IOException;  
import java.io.InputStreamReader;  

public class TicketBookingSystem {
    public static void main(String args[]) throws IOException{
 BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
 Ticket ticket;
  
 for(int i=0; i < 5; i++){
     System.out.println("Enter ticket type, Name and Fare");
     String[] stringList = br.readLine().split(" ");
     ticket = TicketFactory.getTicket(stringList[0], stringList[1],
               Integer.parseInt(stringList[2]));
     ticket.printTicket();
 }
    }
}

Output

Enter ticket type, Name and Fare
INFANT Jack 100
--------TICKET--------
Name : Jack
Ticket Type : INFANT
Fare : 100
Enter ticket type, Name and Fare
ADULT George 500
--------TICKET--------
Name : George
Ticket Type : ADULT
Fare : 500
Enter ticket type, Name and Fare
INFANT Adams 100
--------TICKET--------
Name : Adams
Ticket Type : INFANT
Fare : 100
Enter ticket type, Name and Fare
INFANT Julia 100
--------TICKET--------
Name : Julia
Ticket Type : INFANT
Fare : 100
Enter ticket type, Name and Fare
ADULT Rex 500
--------TICKET--------
Name : Rex
Ticket Type : ADULT
Fare : 500

Best Practices of Flyweight Design Pattern

  • Clearly distinguish between intrinsic state (shared among flyweights) and extrinsic state (not shared). This distinction is crucial for managing the shared and unshared aspects of flyweights.

  • Make the intrinsic state of flyweights immutable. This ensures that once created, the intrinsic state remains constant, promoting shared usage among multiple objects.

  • Centralize the creation of flyweights in a factory class. The factory is responsible for ensuring that shared flyweights are reused and unshared flyweights are created as needed.

  • If the flyweight objects are accessed concurrently by multiple threads, ensure that the implementation is thread-safe. This may involve proper synchronization mechanisms to avoid race conditions.

  • In scenarios where flyweights are frequently created and discarded, consider using object pooling techniques to manage the lifecycle of flyweight objects efficiently.

  • Minimize the initialization overhead of flyweights, especially shared ones. Consider lazy initialization strategies to create shared flyweights only when needed.

  • Allow for dynamic creation of flyweights based on runtime conditions. This flexibility ensures that the flyweight pattern can adapt to changing requirements.

  • Provide mechanisms to handle unshared flyweights, which may have unique characteristics or behaviors. These can be managed alongside shared flyweights in the factory.
Related Topics
State Design Pattern
Prototype Design Pattern
Strategy Design Pattern
Mediator Design Pattern
Facade Design Pattern
List of Design Patterns