Flyweight design pattern falls under the structural design pattern category. Sometimes, our applications hit slow performances. This can be due to various reasons. Instantiating many amounts of heavy objects can be one of the very common reasons for the low system performance. This can be rectified by the flyweight pattern successfully. Flyweight pattern introduces a new object called ‘Flyweight’ to avoid the creation of a large number of heavy and high memory consumption objects. This can be a kind of basic caching mechanism to reduce the overhead.

GoF Definition,

Facilitates the reuse of many fine-grained objects, making the utilization of large numbers of objects more efficient.

In this context, fine-grained objects are the objects that consume large memory and heavy for the pool. Flyweight object act as the one shareable object. Thus, that shareable object takes the place of a large number of fine-grained objects efficiently. In this mechanism, the system requires to store only the flyweight object by sharing common parts of object state among multiple objects. Thus, RAM does not need to store all the other similar heavy objects and can save huge performance overhead.

Feature/Factors to Satisfy

Following basic factors will determine whether your application is in need of the flyweight pattern.

Thousands of new instantiation occurrences of a memory intensive heavy object

The objects created via the heavy object have very similar features in every instantiation occurrences

The object pool contains many similar objects and two objects from the pool don’t differ highly.

The object creation consumes high memory usage causing interruptions in program execution.

Objects have the shareable ability

What is Flyweight Object?

This is the core component of the flyweight design pattern. It provides the full solution for the problem being addressed by this pattern. Flyweight is an object that can be used in place of a number of similar heavyweight objects and it efficiently shares the common information present in the similar object pool. Flyweight object must have shareable features unless it cannot be used as a template representor for similar objects. Flyweight can be used in multiple contexts simultaneously due to this shareable nature. In that case, it acts as an independent object in each context.

It is important to note that flyweight objects are immutable. That is they cannot be modified after the construction occurred. This is to preserve the template nature of the flyweight object in order to support the duplication role. This is achieved via keeping the state of the objects constant.

Let’s briefly identify the state details of the flyweight object.

Two States of Flyweight Design Pattern

State of the flyweight object is a crucial part in designing the solutions out of the flyweight design pattern. The main objective of the flyweight pattern is to reduce the memory load by sharing objects. This is mainly achieved by separating the object properties into two states. Each flyweight object can be divided into two categories as state-independent and state-dependent aka ‘Intrinsic’ and ‘Extrinsic’ respectively.

Intrinsic State

This state contains data that are unchangeable and independent of the context of the flyweight object. Those data can be stored permanently inside the flyweight object. This makes the flyweight object shareable. The intrinsic data are stateless and generally remains unchanged. This feature gives the ability to replicate the flyweight object properties among other similar objects. It’s important to note that flyweight objects should receive their intrinsic state only via constructor parameters not exposing setters or public fields.

Extrinsic State

The extrinsic state represents the context-dependent nature of the flyweight object. This state contains the properties and data applied or calculated on runtime. Thus, those data are not stored in the memory. Since the extrinsic state is context dependent and variant, those objects cannot be shared. Therefore, the client objects are responsible for passing the extrinsic state related data to the flyweight object when needed. Extrinsic state data can be passed to the flyweight object through arguments.

In short, it’s best to consider how each data behaves inside the object when creating the flyweight object. That is we must store unchangeable (intrinsic) within the flyweight object while passing the volatile data (extrinsic) on the fly. This behaviour of the pattern saves us a lot of memory and make the execution far efficient.

Real World Examples of Flyweight Design Pattern

Alphabet characters of a text editor

Let’s just focus on the alphabet characters for this example. Text editors have 26 distinct letters to write words. The editor has to instantiate all 26 letters repeatedly. For instance, when typing ‘Birthday Baby’, there are three occurrences of the letter ‘b’. If instantiate three different characters ‘O’ objects separately it is a complete waste of time, effort and memory because it is the same object base. Only the extrinsic effect of letter ‘b’ has changed. That is one occurrence it is bold-capital ‘b’ and next it is just capital while the final is simple ‘b’. To overcome repeated heavy object creation, we can use a flyweight object. Keeping the base of letter ‘b’ as intrinsic and other styles as extrinsic data. Take a look at Word Processor

Public-switched telephone system

There are several resources that have to be shared among subscribers in this type of telephone networks. The pool contains thousands of resources to cater the subscriber requests. Although, the subscriber is unaware of the number of resources in the pool. The usual resources in a public switched telephone system are dial tone generators, ringing generators and digital resources. When assessing the behaviours of each resource we can find intrinsic and extrinsic data and later can apply flyweight pattern without creating new objects for each subscriber request. Read more here.

JDBC connection pool

This is a fine tech level example of a flyweight behaviour. JDBC connection pool contains a collection of pre-created and cached database connections. Because the process of creating a database connection in an application is an expensive and time-consuming process. Moreover, in most applications, there are thousands of database transactions occurring every day. Catering this requirement cost huge performance overhead if try to create new connections at every request. Instead, connections are created ahead of time like flyweight objects and depending on the user request type extrinsic parameters added on the fly and cater the user need. May be interesting to you: How JDBC Resources and Connection Pools Work Together

Browser component loading and caching

Modern web browsers use this technique to prevent loading the same images twice. When the browser loads a web page, it traverses through all images on that page. The browser loads all new images from the Internet and places them the internal cache. For already loaded images, a flyweight object is created, which has some unique and extrinsic data like position within the page, but everything else (intrinsic) is referenced to the cached one. More about browser component loading and caching – https://stackoverflow.com/questions/11751041/will-browser-download-image-twice-if-its-used-in-both-an-images-src-as-well-as

Class Diagram for Flyweight Design Pattern

Components of Flyweight Design Pattern

Flyweight Interface

This is the base interface for building concrete flyweight classes. This interface contains a method, which takes the extrinsic data as arguments. Thus, the implementing flyweights can receive and use extrinsic data as required. Sometimes, this can be an abstract class.

FlyweightFactory

In this pattern, clients do not create Flyweight objects directly. Instead, FlyweightFactory class creates and manages the flyweight objects in the system. This is due to the existing Flyweight object pool in the memory. When there is a request for a Flyweight object, factory checks for an already created object in the pool, if there is, the reference to that object is returned. If there is no existing object, the factory creates a new object adds it to the pool and return to the client. It also supports the sharing of Flyweight objects

ConcreteFlyweight

This is the most frequent implementation from the Flyweight interface. This implementation must carry the capabilities of the intrinsic state. That is data must be unchangeable and shareable. Objects are stateless in this implementation. Thus, the same ConcreteFlyweight object can be used in different contexts.

UnsharedFlyweight

This concrete implementation facilitates the option to create Flyweight objects that are not shareable. In this options, Flyweight objects can be stateful.

Although the flyweight design pattern enables sharing of information, it is possible to create instances of concrete flyweight classes that are not shared. In these cases, the objects may be stateful.

Client

Client requests a Flyweight object via the FlyweightFactory and uses the returned object. It doesn’t know how the pattern works.

Steps to Implement the Flyweight Design Pattern

Identify the issue through observation or an issue occurred during object creation Identify the particular heavy object that leads the memory issue Identify the shareable (intrinsic) and non-shareable (extrinsic) fields in that heavy object Create the Flyweight interface by assigning intrinsic behaviours appropriately Create concrete shareable Flyweight class and non-shareable Flyweight class (If exists) Create the Flyweight factory class to generate the suitable objects Create the client who uses Flyweight factory to instantiate suitable Flyweight objects upon request

Factory Method Pattern Usage inside Flyweight Design Pattern

As you can see in the class diagram, there is a FlyweightFactory class in the pattern implementation. This is to avoid the unnecessary heavy object creation from the scratch. Pattern maintains an object pool to cater object requests. The client will directly call the factory to get the required object. Then, factory checks whether the required object is in the object pool. If the object exists within the pool, the factory will return it with the added features (extrinsic) on the fly. If the requested object is not inside the pool, the FlyweightFactory class will create a new intrinsic state object, add it to the pool, and return to the client with the requested extrinsic state. Each new instance should be created by the FlyweightFactory since it is the place of management and shareability happens with clients

Code Example

Let’s assume a factory that manufactures teacups. There are about four types of teacups. They differ only by shape and colour. The ingredients and quality are the same. The factory manufactures around 1000 items in one batch for one type of teacup. In this case, creating 4000 teacups from the scratch is an inefficient task. Instead, we can use the Flyweight pattern to save the time and effort. Let’s see the suitable components for the teacups example.

TeacupBaseAbstractClass

This class contains the base methods for creating a teacup. Like adding initial moulding ingredients, mixing formula and applying suitable temperature and time for making the base mixture.

TeacupFactory

This class performs the creation and management of teacups and it’s repository. This is the main component that decides whether to create a brand new cup mould or use the existing template and use for cup creation.

ConcreteTeacup

This class include the methods to create different shape and colour teacups derived from the abstract class.

Customer

Any teacup type requester who wants to have all types of teacups.

Code Samples

TeacupBaseAbstractClass.java

public abstract class TeacupBaseAbstractClass { /* * Following two methods incorporate the extrinsic state to the pattern * by including unshareable features in method parameters. */ public abstract void addColor(String color); public abstract void applyShape(String shape); public abstract void manufactureTeacup(); /* * Following two methods incorporate the intrinsic state to the pattern * by implementing shareable methods for the Teacup production */ public void addIngInitailIngredients() { System.out.println("Adding Base Ingredients for the mold"); } public void applyTemperature() { System.out.println("Applying Temperature"); } public void makeBaseMixture() { System.out.println("Making base mixture"); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 public abstract class TeacupBaseAbstractClass { /* * Following two methods incorporate the extrinsic state to the pattern * by including unshareable features in method parameters. */ public abstract void addColor ( String color ) ; public abstract void applyShape ( String shape ) ; public abstract void manufactureTeacup ( ) ; /* * Following two methods incorporate the intrinsic state to the pattern * by implementing shareable methods for the Teacup production */ public void addIngInitailIngredients ( ) { System . out . println ( "Adding Base Ingredients for the mold" ) ; } public void applyTemperature ( ) { System . out . println ( "Applying Temperature" ) ; } public void makeBaseMixture ( ) { System . out . println ( "Making base mixture" ) ; } }

ConcreteTeacup.java

public class ConcreteTeacup extends TeacupBaseAbstractClass { private String colour; public String getColor() { return color; } public void setColor(String color) { this.color = color; } public String getShape() { return shape; } public void setShape(String shape) { this.shape = shape; } private String shape; public ConcreteTeacup(String color, String shape) { this.color = color; this.shape = shape; } /* * extrinsic state i.e. speed comes from client as method parameter and its * not shared across the Teacup object */ @Override public void addColor(String color) { System.out.println("Adding color " + color + " to the teacup"); } @Override public void applyShape(String shape) { System.out.println("Adding shape " + shape + " to the teacup"); } @Override public void manufactureTeacup() { System.out.println("Teacup: with Color : " + color + " and " + shape); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 public class ConcreteTeacup extends TeacupBaseAbstractClass { private String colour ; public String getColor ( ) { return color ; } public void setColor ( String color ) { this . color = color ; } public String getShape ( ) { return shape ; } public void setShape ( String shape ) { this . shape = shape ; } private String shape ; public ConcreteTeacup ( String color , String shape ) { this . color = color ; this . shape = shape ; } /* * extrinsic state i.e. speed comes from client as method parameter and its * not shared across the Teacup object */ @Override public void addColor ( String color ) { System . out . println ( "Adding color " + color + " to the teacup" ) ; } @Override public void applyShape ( String shape ) { System . out . println ( "Adding shape " + shape + " to the teacup" ) ; } @Override public void manufactureTeacup ( ) { System . out . println ( "Teacup: with Color : " + color + " and " + shape ) ; } }

TeacupFactory.java

public class TeacupFactory { private static final HashMap<String, ConcreteTeacup> cups = new HashMap(); public static ConcreteTeacup getTeacup(String id) { ConcreteTeacup concretecup = (ConcreteTeacup) cups.get(id); if (concretecup == null) { if (id.equals("001")) { concretecup = new ConcreteTeacup("Red", "Traingle"); } else if (id.equals("002")) { concretecup = new ConcreteTeacup("Green", "Round"); } else if (id.equals("003")) { concretecup = new ConcreteTeacup("Blue", "Square"); } concretecup.addIngInitailIngredients(); concretecup.applyTemperature(); concretecup.makeBaseMixture(); cups.put(id, concretecup); } return concretecup; } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 public class TeacupFactory { private static final HashMap < String , ConcreteTeacup > cups = new HashMap ( ) ; public static ConcreteTeacup getTeacup ( String id ) { ConcreteTeacup concretecup = ( ConcreteTeacup ) cups . get ( id ) ; if ( concretecup == null ) { if ( id . equals ( "001" ) ) { concretecup = new ConcreteTeacup ( "Red" , "Traingle" ) ; } else if ( id . equals ( "002" ) ) { concretecup = new ConcreteTeacup ( "Green" , "Round" ) ; } else if ( id . equals ( "003" ) ) { concretecup = new ConcreteTeacup ( "Blue" , "Square" ) ; } concretecup . addIngInitailIngredients ( ) ; concretecup . applyTemperature ( ) ; concretecup . makeBaseMixture ( ) ; cups . put ( id , concretecup ) ; } return concretecup ; } }

Customer.java

public class Customer { private static final String id[] = { "001", "002", "003" }; private static final String shapes[] = { "Round", "Square", "Traingle" }; private static final String colors[] = { "Red", "Green", "Blue" }; /* * Generating 30 Teacups */ public static void main(String[] args) { for (int i = 0; i < 20; ++i) { ConcreteTeacup teacup = TeacupFactory.getTeacup(getRandomId()); if (teacup != null) { teacup.addColor(getRandomColor()); teacup.setShape(getRandomShape()); teacup.manufactureTeacup(); } } } private static String getRandomId() { return id[(int) (Math.random() * id.length)]; } private static String getRandomShape() { return shapes[(int) (Math.random() * shapes.length)]; } private static String getRandomColor() { return colors[(int) (Math.random() * colors.length)]; } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 public class Customer { private static final String id [ ] = { "001" , "002" , "003" } ; private static final String shapes [ ] = { "Round" , "Square" , "Traingle" } ; private static final String colors [ ] = { "Red" , "Green" , "Blue" } ; /* * Generating 30 Teacups */ public static void main ( String [ ] args ) { for ( int i = 0 ; i < 20 ; ++ i ) { ConcreteTeacup teacup = TeacupFactory . getTeacup ( getRandomId ( ) ) ; if ( teacup ! = null ) { teacup . addColor ( getRandomColor ( ) ) ; teacup . setShape ( getRandomShape ( ) ) ; teacup . manufactureTeacup ( ) ; } } } private static String getRandomId ( ) { return id [ ( int ) ( Math . random ( ) * id . length ) ] ; } private static String getRandomShape ( ) { return shapes [ ( int ) ( Math . random ( ) * shapes . length ) ] ; } private static String getRandomColor ( ) { return colors [ ( int ) ( Math . random ( ) * colors . length ) ] ; } }

When to use the Flyweight Design Pattern

When an application uses a large number of objects

When there is a repetitive creation of heavy objects

When there are memory allocation issues in the application

When the many groups of objects can be replaced by a few shared objects

Advantages of Flyweight Design Pattern

Reduce memory usage by sharing heavy objects

Improved data caching for higher response time

Increased performance due to a lesser number of heavy objects

Drawbacks of Flyweight Design Pattern

This makes garbage collection unfriendly solution, as per the implementation, shared objects may be not eligible for garbage collection

Reduce memory usage by sharing heavy objects

Improved data caching for higher response time

Increased performance due to a lesser number of heavy objects

Other Related Patterns

There are few other design patterns, which relates and used by the flyweight pattern.

Flyweight pattern directly uses the factory pattern to instantiate the objects. You can check our factory pattern article from this link – https://www.javagists.com/abstract-factory-pattern-in-java

Flyweight pattern is a bit similar to Singleton pattern when thinking about the usage of one object creation to represent a lot. However, there are fundamental deference’s like singleton objects are mutable while Flyweight objects are immutable, there is only one, and only one singleton object for a system while there can be multiple Flyweight objects depending on the intrinsic state.

It is identified that ‘State’ and ‘Strategy’ pattern objects can be implemented as Flyweight objects.

As a contrasting feature, Flyweight shows how to create many small objects while the facade pattern shows how to create a single object represent an entire subsystem.

As usual we have uploaded the code used in this article to github. You can find it here.

Share this: Facebook

LinkedIn

Twitter

Tumblr

Pinterest



Like this: Like Loading...