Back to: Java Design Patterns
Flyweight Design Pattern in Java with Examples
In this article, I am going to discuss Flyweight Design Pattern in Java with Examples. Please read our previous article where we discussed Decorator Design Pattern in Java with Examples. The Flyweight Design Pattern falls under the category of the Structural Design Pattern. In this article, we will explore the fundamental principles and benefits of the Flyweight Design Pattern in Java, highlighting its ability to optimize resource utilization while maintaining a flexible and scalable codebase.
What is Flyweight Design Pattern?
In software development, memory consumption and performance optimization are vital considerations, especially when dealing with large-scale applications or systems with numerous similar objects. The Flyweight design pattern offers an efficient solution to these challenges. By sharing a common state among multiple objects, the Flyweight Design Pattern minimizes memory usage and improves performance.
The Flyweight design pattern is a structural pattern that aims to minimize memory usage by sharing common data across multiple objects. It achieves this by separating the intrinsic (shared) and extrinsic (context-specific) states of an object. The intrinsic state is stored in a shared flyweight object, while the extrinsic state is maintained externally by the client. By isolating the shared state, the pattern allows multiple objects to share the same flyweight, reducing memory requirements.
This Design Pattern is basically used to reduce the number of objects created, decrease the memory footprint, and increase the overall performance of the application. That means Flyweight Design Pattern tries to reduce the already existing similar kind of objects by storing them in the cache and creating a new object when no matching object is found.
For example, you have one image, and you want thousands of copies of that image. There are two ways to achieve that. In the first approach, we can get the printouts 1000 times that image. The second approach, we can get a printout of that image and then we can use that printout and then we can take 999 xeroxes of that image. Let’s suppose, if the printout for one image is 2 USD, then the total amount required is 1000*2=2000USD. If the Xerox price is 1 USD, then the total amount required is 999*1=999 USD, and one printout is 2 USD, so a total of 1001 USD. So, we can save much amount if we follow the second approach. This is also the same in programming. And we can achieve this by using the Flyweight Design Pattern in Java.
Note: Intrinsic states are things that are Constants and Stored in Memory. On the other hand, Extrinsic states are things that are not constant and need to be calculated on the Fly, and hence they cannot be stored in memory.
Real-Time Example to Understand Flyweight Design Pattern:
Please have a look at the following image for a better understanding of the Flyweight Design Pattern. As you can see in the following image, we created and stored one circle object in the Cache. Here, the circle object that we stored in the cache does not have any color. Suppose, let’s say we have to create 90000 circle objects which are in green color and 90000 circle objects in blue color. Again, we have to create 80000 circle objects which are in orange color, and 70000 circle objects in black color. If you notice all the circle object shapes are the same only the color is changed.
As per the Flyweight Design Pattern, we can improve the performance by creating the circle object only one time and reusing that circle object many times to create different types of colors. Suppose, you want to create 90000 circle objects with green color, then what you can do is, instead of creating new circle objects every time and filling them with green color, you can get the circle object from the Cache and fill that object with green color. In the same way, you can create 90000 circle objects with green color. So, in this way, we can improve the performance of the application using the Flyweight Design Pattern in Java.
Example to Understand Flyweight Design Pattern in Java
A real-world example where the Flyweight pattern can be applied is a road. In this scenario, the application needs to handle a large number of vehicles on the road. Instead of creating a separate object for each vehicle, the Flyweight pattern can be used to share common characters and optimize memory usage.
The intrinsic state of a character, such as its type, can be considered as shared and stored in a flyweight object. The flyweight object contains the common characteristics of the character, while the extrinsic state, such as its color, is managed separately.
By using the Flyweight pattern in the road application, memory usage can be optimized, especially when dealing with a large number of vehicles. It improves the performance of the application by reducing the memory footprint. The UML Diagram of this example is given below using Flyweight Design Pattern.
Implementing Flyweight Design Pattern in Java
Step 1: Create a new directory to store all the class files of this project.
Step 2: Open VS Code and create a new project, called flyweight.
Step 3: In the project, create a new file called Vehicle.java. Add the following code to the file:
This is the interface from which other concrete classes will be created.
Step 4: In the project, create a new file called Bike.java. Add the following code to the file:
Step 5: In the project, create a new file called Car.java. Add the following code to the file:
Step 6: In the project, create a new file called VehicleFactory.java. Add the following code to the file:
This class will be called whenever a new vehicle needs to be created is to be made.
Step 7: In the project, create a new file called FlyweightPatternDemo.java. This class will contain the main() function.
Step 8: Write the main() function in FlyweightPatternDemo.java:
In the main function, we create 10 vehicles with random types, engine capacities, and colors. We then use the drive() function to print out the created vehicles.
Step 9: Compile and execute the application. Ensure compilation is successful. Verify that the program works as expected.
Congratulations! You now know how to implement flyweight patterns!
UML Diagram of Flyweight Design Pattern:
Now, let us see the Flyweight Design Pattern UML Diagram Components with our Example so that you can easily understand the UML Diagram.
The classes can be described as follows:
- Object: This is the interface that provides the basic function definitions for each object.
- ConcreteObject: This is the class that implements the functions defined in the aforementioned interface.
- Handler: This class acts as the middleman between the driver class and the objects.
- DriverClass: This class contains the main() function and is responsible for simulating the application.
The Complete Example Code of Flyweight Design Pattern in Java
Bike.java
public class Bike implements Vehicle { private String col; private float cap; @Override public void color(String col) {this.col = col;} @Override public void engine(float cap) {this.cap = cap;} @Override public void drive() { System.out.println("A " + col + " coloured bike with and engine capacity " + cap + " is driving."); } }
Car.java
public class Car implements Vehicle { private String col; private float cap; @Override public void color(String col) {this.col = col;} @Override public void engine(float cap) {this.cap = cap;} @Override public void drive() { System.out.println("A " + col + " coloured car with and engine capacity " + cap + " is driving."); } }
FlyweightPatternDemo.java
import java.util.Random; public class FlyweightPatternDemo { private static String[] colors = {"blue", "white", "brown", "red", "orange", "black"}; private static String[] typeOfVehicles = {"Bike", "Car"}; public static String getRandomColor() {return colors[new Random().nextInt(colors.length)];} public static String getRandomVehicleType() {return typeOfVehicles[new Random().nextInt(typeOfVehicles.length)];} public static float getRandomEngineCapacity() {return new Random().nextInt(5000);} public static void main(String[] args) { for (int i = 0; i < 10; i++) { Vehicle v = VehicleFactory.getVehicle(getRandomVehicleType()); v.color(getRandomColor()); v.engine(getRandomEngineCapacity()); v.drive(); } } }
Vehicle.java
public interface Vehicle { public void color(String col); public void engine(float cap); public void drive(); }
VehicleFactory.java
import java.util.HashMap; public class VehicleFactory { private static HashMap<String, Vehicle> hm = new HashMap<String, Vehicle>(); public static Vehicle getVehicle(String typeOfVehicle) { Vehicle v = null; if (hm.containsKey(typeOfVehicle)) v = hm.get(typeOfVehicle); else { switch(typeOfVehicle) { case "Car": System.out.println("Car created"); v = new Car(); break; case "Bike": System.out.println("Bike created"); v = new Bike(); break; } hm.put(typeOfVehicle, v); } return v; } }
Advantages of Flyweight Design Pattern in Java:
Some of the Advantages of using the Flyweight Design Pattern in Java are as follows:
- Memory Optimization: The Flyweight pattern significantly reduces memory consumption by sharing common data among multiple objects. Instead of replicating the shared state in each object, the pattern ensures that only a single instance is used, resulting in efficient memory utilization.
- Improved Performance: With reduced memory usage, the Flyweight pattern also improves performance. By minimizing the creation and destruction of flyweight objects, the system can process operations faster, resulting in better overall performance.
- Scalability and Flexibility: The pattern enables the system to scale and accommodate a large number of objects without excessive memory requirements. This scalability is particularly beneficial in situations where a system needs to handle a vast number of similar objects, such as rendering multiple instances of graphical elements in a game or generating reports for a large dataset.
- Separation of Intrinsic and Extrinsic State: The Flyweight pattern promotes a clear separation between the intrinsic and extrinsic states of objects. This separation allows for greater flexibility, as the extrinsic state can be modified independently, while the intrinsic state remains shared among objects.
Disadvantages of Flyweight Design Pattern in Java:
Some of the Disadvantages of using the Flyweight Design Pattern in Java are as follows:
- Limited Customization: The primary purpose of the facade pattern is to provide a simplified interface to a complex system. However, this simplicity can come at the cost of flexibility and customization. By abstracting the underlying subsystems, the facade may not expose all the features and options available in each subsystem. Consequently, clients requiring fine-grained control or specific functionalities may find the facade limiting.
- Increased Dependency: Introducing a facade to simplify the interaction with subsystems can create a higher level of dependency. Clients become reliant on the facade, tightly coupling their code with it. If changes are made to the facade or the subsystems it encapsulates, it may require modifications in multiple client implementations. This increased dependency can make the codebase more fragile and introduce a maintenance burden.
- Performance Overhead: In certain scenarios, the facade pattern may introduce performance overhead. As the facade acts as an intermediary between clients and subsystems, it adds an extra layer of abstraction. This additional layer of indirection can impact the overall system performance, especially in situations where high efficiency is critical. It is essential to evaluate the trade-off between simplicity and performance before applying the facade pattern.
- Reduced Visibility of Subsystem Details: While the facade pattern aims to hide the complexities of subsystems, it can also reduce the visibility of underlying implementation details. This lack of visibility may make it challenging for developers to diagnose and debug issues that arise within the subsystems. If troubleshooting or customization requires a deeper understanding of the subsystems, the facade may hinder these efforts.
- Increased Complexity for Complex Systems: While the facade pattern simplifies the interaction with subsystems, it may not be the most suitable solution for extremely complex systems. In such cases, the facade itself might become intricate and challenging to manage. Maintaining a high-level interface that adequately encapsulates all subsystems while addressing diverse client needs can become complex and cumbersome.
In the next article, I am going to discuss Proxy Design Pattern in Java with Examples. Here, in this article, I try to explain Flyweight Design Pattern in Java with Examples. I hope you understood the need for and use of the Flyweight Design Pattern in Java.