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 interface defines methods that are common to all concrete flyweights. It may include functionalities for setting or retrieving intrinsic state shared among multiple objects.

  • Concrete Flyweight : Representing the concrete implementation of the flyweight interface, this component holds the intrinsic state shared across various objects.

  • Unshared Concrete Flyweight : In certain scenarios, not all flyweights can be shared. The unshared concrete flyweight embodies objects with intrinsic states that cannot be shared.

  • Flyweight Factory: : Responsible for the management of flyweight objects, the flyweight factory ensures proper sharing. It may encompass methods for the creation or retrieval of flyweights.

  • Client : The client is tasked with utilizing flyweights, maintaining the extrinsic state that is not shared among flyweights.

Advantages of Flyweight Design Pattern

  • Memory Usage Optimization : The Flyweight pattern's main benefit is its ability to optimize memory utilization. The pattern minimizes total memory footprint by having several objects share shared intrinsic state.

  • Enhanced Scalability : By enabling the effective management and sharing of a large number of flyweights, the flyweight pattern improves scalability. This is very helpful for systems that have a lot of items that need to be handled dynamically.

  • Improved Performance : Since the construction and upkeep costs of shared items are substantially cheaper than those of distinct objects, sharing flyweights improves performance. This is particularly useful in situations where there are a lot of similar objects.

  • Simplified Object Creation : By centralizing accountability in a factory, the pattern makes the process of creating things simpler. This centralization permits the usage of shared flyweights and guarantees uniform creation operations.

  • Flexible and Extensible :The Flyweight pattern exhibits both flexibility and extensibility. It is possible to add new flyweights to the system without changing the current ones. This adaptability is very helpful for systems that are changing and have shifting requirements.

  • Reduction of Redundant State : By distributing flyweights among several objects, redundant state is reduced. By sharing the maintenance of the intrinsic state, consistency is ensured and duplication is minimized.

  • Promotion of Reusability : The pattern encourages the reusing of shared flyweights, which helps to make existing objects more reusable. The codebase becomes more modular and manageable as a result of this reusability.

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