Flyweight Design Pattern

Robin Srivastava
5 min readFeb 27, 2024

--

Why do we even need design patterns?

The bare minimum criteria for an application to qualify as well implemented is the provision to incorporate ever changing business requirements with as minimal code change as possible.

This helps in reducing overall build lifecycle overhead and hence the total cost of software development.
Understanding and leveraging design patterns help us prevent this overhead and make our implementations resilient.

Apart from above, design patterns are the building blocks of any framework.
Hence, mastering it would enable us to quickly get used to new frameworks in this ever changing software realm.

What is a flyweight design pattern ?

Flyweight design pattern is an ideal candidate to be used in any business flow where in there is a memory heavy object that is being used for
some computation.

However, there is a scope to share that bulky object among different entities instead of creating new object for each entity.
e.g. Let’s say we are creating an application similar to Google Maps where there is a provision to mark source and destination location.
In this use case, we can leverage flyweight design pattern as the marker object ( the one which pictorially represent source and destination location) can be reused between source and destination entities instead of creating a new marker for each location.

Money Transfer in different currency use case :

It is a very common phenomenon in this hyperconnected world to transfer money from one country to another and in a different currency.

For our article, let’s assume we are building a money transfer service which debits money from one account in source country to a different account in
target country and it needs to transfer money in equivalent target currency which more often than not will be different from source country.

Let’s explore how flyweight design pattern can be leveraged for memory efficient implementation of above use case.

Building blocks of Flyweight design pattern :

Flyweight design pattern is one of the structural design pattern which basically is a time tested way of organizing objects to cater to ever evolving use cases in an extendible and resilient way.

It has primarily 3 actors :

1. Flyweight Component : This is basically the instance that would represent the shared properties which will be reused among different entities.

2. Factory Component : This is the component that decides whether a new Flyweight component needs to be created or already existing one can be reused.

3. Service Component : This is the component that provides an interface to client program to procure flyweight components.

Implementation of aforementioned use case :

Let’s implement our money transfer use case to demonstrate flyweight design pattern.

Let’s start defining our first actor Flyweight Component which essentially represents the shared object and is defined as follows :

package structural.flyweight;

public class CurrencyExchange {

private final String sourceCurrency;
private final String targetCurrency;

public CurrencyExchange(String sourceCurrency, String targetCurrency) {
this.sourceCurrency = sourceCurrency;
this.targetCurrency = targetCurrency;
}

public double getExchangeFactor(){
// Assumption : Conversion logic from one currency to another.
double exchangeFactor = 2.6;
System.out.printf("Exchange factor between %s %s is %s \n", sourceCurrency, targetCurrency, exchangeFactor);
return exchangeFactor;
}
}

It basically returns the exchange factor using which source currency can be converted into target currency.

Now, the next actor to be defined would be the Factory Component which decides whether to create a new Currency Exchange instance or reuse already created one.

It is defined as follows :

package structural.flyweight;

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

public class PaymentFactory {
private final Map<String, CurrencyExchange> currencyExchangeCache = new HashMap<>();


public CurrencyExchange getCurrencyExchange(String key){
if(currencyExchangeCache.get(key) == null){
String[] currencies = key.split(":");
currencyExchangeCache.put(key,new CurrencyExchange(currencies[0],currencies[1]));
}
return currencyExchangeCache.get(key);
}
}

Essentially, it maintains a cache of already initiated instance and accordingly decides whether to create new instance or not.

The final component of our implementation is the actor which exposes the API for client program to get instance of Flyweight Component and is defined as follows :

package structural.flyweight;

public class PaymentService {
private final PaymentFactory paymentFactory;

public PaymentService(PaymentFactory paymentFactory) {
this.paymentFactory = paymentFactory;
}

public CurrencyExchange getExchangeFactor(String exchangeKey){
return paymentFactory.getCurrencyExchange(exchangeKey);
}
}

Using getExchangeFactor method, it retrieves the corresponding Currency Exchange instance which is used to figure out final amount that needs to be transferred.

Let’s define the client program which would essentially represent money transfer process and leverage all our actors defined above.
It is defined as follows :

package structural.flyweight;

public class Bill {
private final CurrencyExchange currencyExchange;
private final double amount;

public Bill(CurrencyExchange currencyExchange, double amount) {
this.currencyExchange = currencyExchange;
this.amount = amount;
}

public void transferMoney(){
double exchangeFactor = currencyExchange.getExchangeFactor();
System.out.printf("Amount : %s transferred with exchange factor %s \n", amount, exchangeFactor);
}
}

And different currencies supported by our application are represented as enum as follows :

package structural.flyweight;

public enum Currency {
USD,
INR,
AED,
EUR,
YEN
}

With all the building blocks in place, let’s mock the money transfer process as follows :

package structural.flyweight;

public class Driver {
public static void main(String[] args) {
PaymentFactory paymentFactory = new PaymentFactory();
PaymentService paymentService = new PaymentService(paymentFactory);

Bill transferInrToUs = new Bill(paymentService.getExchangeFactor("INR:USD"),35000);
Bill transferAedToInr = new Bill(paymentService.getExchangeFactor("AED:INR"), 2300);

Bill transferUsToInr = new Bill(paymentService.getExchangeFactor("INR:USD"), 3000);

/*
* Since, transferInrToUs and transferUsToInr have same exchange key so we can reuse CurrencyExchange
* object.
* Thus, removing redundancy and saving memory
* */
transferInrToUs.transferMoney();
transferAedToInr.transferMoney();
transferUsToInr.transferMoney();

}
}

Each instance of the bill represents an activity of money transfer.
P.S. : scope of the article is to elucidate flyweight design pattern and not get into low level implementation details.

On careful consideration, we would observe that objects transferInrToUs and transferUsToInr have same exchange key so the ExchangeCurrency instance can be shared instead of creating new instance for each of the billing activity.
Thus, saving memory and preventing performance bottleneck and crash as well if application scales.

Output of above code would be as follows :

Inefficient way of implementing the same :

Another intuitive way of implementing aforementioned use case would have been to create a separate currency exchange object for each instance of the bill.
However, this would unnecessary bloat up the memory with redundant currency exchange objects and may lead to degraded application performance and in some cases crash as well.

It may look trivial for applications where incoming traffic is low but think of the systems of the scale of WhatsApp where a minor improvement in memory consumption can lead to compounded memory optimization.

UML diagram for the same :

To remember the actors visually, we can refer following UML diagram :

Above Diagram states that Client references Service Component which in turn references Factory Component which in turn references Flyweight Component via composition.

That’s all folks :)

I intend to demystify fundamental building blocks of Software Architecture and Development primarily focused on Backend.
Do subscribe if you find my articles helpful.

--

--