In this article of Java Design Pattern, we will look at the Flyweight Design Pattern. It is one of the Structural Design Patterns. We’ll learn about what this pattern is all about. After that, we’ll look at the pattern’s design advantages, usage, and disadvantages.
Flyweight Design Pattern
The flyweight design pattern is applied when we need to construct a large number of objects belonging to a class. Since each object requires memory, the flyweight design pattern can be used to share objects and lessen the demand for memory in low-memory devices like mobile devices or embedded systems. In essence, a flyweight object has two types of characteristics: intrinsic and extrinsic.
The flyweight object stores and shares an intrinsic state property that is not dependent on the context of the flyweight. We should make intrinsic states immutable as the best practice. Extrinsic states cannot be transmitted since they depend on the context of the flyweight. Client objects must provide the flyweight object with the extrinsic state when creating the object because they maintain it.
We must separate Object attributes into intrinsic and extrinsic qualities to apply the flyweight pattern. Extrinsic attributes are set by client code and are used to carry out specific tasks, whereas intrinsic properties make an object unique. An Object Circle, for instance, may have external characteristics like color and breadth.
We must build a Flyweight factory that outputs shared objects to apply the flyweight pattern. Let’s imagine, for the sake of our example, that we need to draw something with lines and ovals. As a result, we will have a Shape interface and its actual implementations, Line and Oval. In contrast to Line, the oval class has an intrinsic characteristic that determines whether or not to fill the Oval with a specific color.
1. When to use Flyweight Design Pattern
The flyweight design pattern is used:
- When the application should create a large number of objects.
- When the process of creating an object might take a lot of time and requires a lot of memory.
- When the extrinsic attributes of an Object should be defined by the client software. The properties of an object can be separated into intrinsic and extrinsic properties.
2. Flyweight Pattern using Java
In the aforementioned example, we’re creating a Paint Brush application where the customer may choose from three different brush types: THICK, THIN, AND MEDIUM. Only the content color will alter between each thick (or thin or medium) brush’s drawings of the content.Let’s see what are the steps to implement the flyweight design pattern in Java.
Step1: First, let’s create an interface Brush to draw the paintings:
public interface Brush {
public void setColor(String color);
public void draw(String content);
}
Step 2: Next, let’s create ENUM
BrushSize for brush sizes.
public enum BrushSize {
THIN,
MEDIUM,
THICK
}
Step 3: Next, let’s create implementing classes ThickBrush
, ThinBrush
, and MediumBrush
. They will implement the Brush
interface.
public class ThickBrush implements Brush {
/*
intrinsic state - shareable
*/
final BrushSize brushSize = BrushSize.THICK;
/*
extrinsic state - supplied by the client
*/
private String color = null;
public void setColor(String color) {
this.color = color;
}
@Override
public void draw(String content) {
System.out.println("Drawing '" + content + "' in thick color : " + color);
}
}
ThinBrush.java
public class ThinBrush implements Brush {
/*
intrinsic state - shareable
*/
final BrushSize brushSize = BrushSize.THIN;
/*
extrinsic state - supplied by the client
*/
private String color = null;
public void setColor(String color) {
this.color = color;
}
@Override
public void draw(String content) {
System.out.println("Drawing ‘" + content + "' in thin color : " + color);
}
}
MediumBrush.java
public class MediumBrush implements Brush {
/*
intrinsic state - shareable
*/
final BrushSize brushSize = BrushSize.MEDIUM;
/*
extrinsic state - supplied by the client
*/
private String color = null;
public void setColor(String color) {
this.color = color;
}
@Override
public void draw(String content) {
System.out.println("Drawing '" + content + "' in medium color : " + color);
}
}
Here, the client will supply the extrinsic attribute of brush color; else, the brush will have no differences. Therefore, in essence, we will only produce a certain size of the brush when the color is different. We will reuse it when a different client or situation calls for that brush size and color.
Step 4: Next, let’s Create the factory BrushFactory
that can provide Brush
objects.
public class BrushFactory {
private static final HashMap < String, Brush > brushMap = new HashMap < > ();
public static Brush getThickBrush(String color) {
String key = color + "-THICK";
Brush brush = brushMap.get(key);
if (brush != null) {
return brush;
} else {
brush = new ThickBrush();
brush.setColor(color);
brushMap.put(key, brush);
}
return brush;
}
public static Brush getThinBrush(String color) {
String key = color + "-THIN";
Brush brush = brushMap.get(key);
if (brush != null) {
return brush;
} else {
brush = new ThinBrush();
brush.setColor(color);
brushMap.put(key, brush);
}
return brush;
}
public static Brush getMediumBrush(String color) {
String key = color + "-MEDIUM";
Brush brush = brushMap.get(key);
if (brush != null) {
return brush;
} else {
brush = new MediumBrush();
brush.setColor(color);
brushMap.put(key, brush);
}
return brush;
}
Step 5: Next, let’s create the client program FlyweightPatternDemo
.
public class FlyweightPatternDemo {
public static void main(String[] args) {
// New thick Red Brush
Brush redThickBrush1 = BrushFactory.getThickBrush("RED");
redThickBrush1.draw("Hello There !!");
// Red Brush is shared
Brush redThickBrush2 = BrushFactory.getThickBrush("RED");
redThickBrush2.draw("Hello There Again !!");
System.out.println("Hashcode: " + redThickBrush1.hashCode());
System.out.println("Hashcode: " + redThickBrush2.hashCode());
// New thin Blue Brush
Brush blueThinBrush1 = BrushFactory.getThinBrush("BLUE"); //created new pen
blueThinBrush1.draw("Hello There !!");
// Blue Brush is shared
Brush blueThinBrush2 = BrushFactory.getThinBrush("BLUE"); //created new pen
blueThinBrush2.draw("Hello There Again!!");
System.out.println("Hashcode: " + blueThinBrush1.hashCode());
System.out.println("Hashcode: " + blueThinBrush2.hashCode());
// New MEDIUM Yellow Brush
Brush yellowThinBrush1 = BrushFactory.getMediumBrush("YELLOW"); //created new pen
yellowThinBrush1.draw("Hello There !!");
// Yellow brush is shared
Brush yellowThinBrush2 = BrushFactory.getMediumBrush("YELLOW"); //created new pen
yellowThinBrush2.draw("Hello There Again!!");
System.out.println("Hashcode: " + yellowThinBrush1.hashCode());
System.out.println("Hashcode: " + yellowThinBrush2.hashCode());
}
}
Here, as you can see the hashcodes
of brushes and you will find that they are shared as they are using the same color
3. Class Diagram
3.1. Real World Examples
Let’s take a look at some of the real world example of the Flyweight Design Pattern.
- Consider a brush that may be used with or without a refill. A brush can be used to produce drawings with N number of colors because a refill can be of any color. Here Brush may be a flyweight with an external characteristic such as a refill. All other characteristics, such as the brush’s body and pointer, may be intrinsic characteristics shared by all brushes. The only other way to identify a brush is by the color of its refill.
- The same instance of red brush can be used by any application modules that require access to one (shared object). Only when a different color brush is required will the application module contact the flyweight factory to order another brush.
- Another example of Flyweight is a string pool, where constant String objects will be stored, and when a string with specific content is required, the runtime will, if possible, return a reference to an already-existing string constant from the pool.
- We can use an image on a webpage many times in browsers. The image will only be loaded once by the browser; subsequent loads will make use of the cached version. Currently, the same image is used numerous times. Due to its fixed nature and share-ability, its URL is an intrinsic characteristic. Extrinsic properties, such as an image’s position coordinates, height, and width, change depending on the location (context) in which they must be presented
3.2. Flyweight Pattern Advantages
- Lowers the memory requirements of large, identically controllable objects.
- less “full but comparable things” are present overall in the system.
- Provides a centralized method for managing the states of numerous “virtual” objects.
3.3 Challenges
- We must give this flyweight configuration enough time. The initial cost of design time and expertise may be high.
- We take a common template class from the current objects and use it to make flyweights. It can be challenging to debug and maintain this additional layer of code.
- The flyweight pattern is frequently paired with singleton factory implementation, and additional expense is needed to protect the singularity.
4. Flyweight Pattern Vs Singleton Pattern
We can keep just one item in the system thanks to the singleton design Pattern. In other words, once the necessary object is produced, we are unable to produce any more. The existing object must be used throughout the entire application. When it is necessary to build a large number of objects that are similar but differ based on an extrinsic attribute supplied by the client, the flyweight pattern is utilized.
Summary
In this post, we talked about the Flyweight Design Pattern. We saw the real-world examples along with what are some advantages and disadvantages of using this pattern. We also saw a Java implementation for this design pattern. You can always check our GitHub repository for the latest source code.
The ValueOf() methods in all wrapper classes employ cached objects, demonstrating the application of the Flyweight design principle. The Java String class’s implementation of the String Pool is the best case study.