Using Flyweight Design Pattern In Java
In this article, explore the Flyweight design pattern in Java.
Join the DZone community and get the full member experience.
Join For FreeI am here to discuss another structural design pattern named Flyweight Design Pattern.
Flyweight Design Pattern
- The Flyweight Design Pattern is a Structural Design Pattern and one of the Gang of Four design patterns.
- The Flyweight pattern is used to minimize the memory usage by sharing as much data as possible with other similar objects.
- The Flyweight pattern provides a way to reduce the number of objects created and to decrease memory footprint and increase performance.
- The Flyweight pattern tries to reuse already existing similar kind objects by storing them in a collection which act like a cache and creates new object when no matching object is found.
- The Flyweight objects we create as immutable. This means that they cannot be modified once they have been constructed. Making flyweight objects as immutable helps while sharing them with other objects.
- The data structures for graphical representation of characters in a Word Processor is a very good classical example of Flyweight pattern.
- The String Interning is another very good example of Flyweight pattern.
- The Flyweight pattern helps us avoiding having large number of objects and allow us to effectively used the created objects by reusing them as much as possible.
- The Flyweight object essentially has two different kind of attributes –
- Intrinsic - An intrinsic (invariant) state attribute is stored and shared in the flyweight object. It is independent of flyweight’s context. So, as the best practice we should make intrinsic states immutable.
- Extrinsic - An extrinsic (variant) state attribute does not store and share in the flyweight object because it depends on flyweight’s context and varies as context change. Generally, we store and maintain the extrinsic state in the Client objects. We need to pass this extrinsic state to the flyweight object for object creation and processing.
Coffee Shop Example Without Using Flyweight Design Pattern
Suppose there is a Coffee shop that serves coffee with different flavors and latte art.
Here is the code for CoffeeFlavour enum to provide supported flavors of coffee.
package org.trishinfotech.flyweight;
public enum CoffeeFlavour {
CAPPUCCINO("Cappuccino"), ESPRESSO("Espresso"), FRAPPUCCINO("Frappuccino"), AFFOGATO("Affogato"), LATTE("Latte");
private String name;
CoffeeFlavour(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
Also here the code for CoffeeLatteArt enum to provide supported latte art of coffee. Latte art is a method of preparing coffee created by pouring microfoam into a shot of espresso and resulting in a pattern or design on the surface of the latte. It can also be created or embellished by simply "drawing" in the top layer of foam.
xxxxxxxxxx
package org.trishinfotech.flyweight;
public enum CoffeeLatteArt {
DISNEY("Disney"), LITTLE_BUNNY("Little Bunny"), FRENCH("French"), BARISTA_SWAG("Barista Swag"),
FISHBONE("Fishbone"), MALAYSIAN_BUDDHA("Malaysian Buddha"), CAT("Cat");
private String name;
CoffeeLatteArt(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
Below is the code for Coffee class. I made Coffee as immutable object.
x
package org.trishinfotech.flyweight.without;
import org.trishinfotech.flyweight.CoffeeFlavour;
import org.trishinfotech.flyweight.CoffeeLatteArt;
public class Coffee {
protected final CoffeeFlavour flavourName;
protected final CoffeeLatteArt latteArt;
protected Coffee(CoffeeFlavour flavourName, CoffeeLatteArt latteArt) {
super();
this.flavourName = flavourName;
this.latteArt = latteArt;
}
public CoffeeFlavour getFlavourName() {
return flavourName;
}
public CoffeeLatteArt getLatteArt() {
return latteArt;
}
}
Also, to make the example comparable with another example by using Flyweight pattern, I wrote a factory named CoffeeFactory which will:
- Allow using restricted access to the Coffee constructor (protected).
- Provide a factory method named makeCoffee to create objects of Coffee class.
- Create and maintain a collection of total number of Coffee objects created.
xxxxxxxxxx
package org.trishinfotech.flyweight.without;
import java.util.ArrayList;
import java.util.List;
import org.trishinfotech.flyweight.CoffeeFlavour;
import org.trishinfotech.flyweight.CoffeeLatteArt;
public class CoffeeFactory {
protected static List<Coffee> coffeeList = new ArrayList<Coffee>();
public static Coffee makeCoffee(CoffeeFlavour flavourName, CoffeeLatteArt latteArt) {
Coffee coffee = new Coffee(flavourName, latteArt);
System.out.printf("Making '%s' with Latte Art '%s'.\n", coffee.getFlavourName(), coffee.getLatteArt());
coffeeList.add(coffee);
return coffee;
}
public static int getNumberOfCoffee() {
return coffeeList.size();
}
}
Here's the code for Order class:
x
package org.trishinfotech.flyweight.without;
import org.trishinfotech.flyweight.CoffeeFlavour;
import org.trishinfotech.flyweight.CoffeeLatteArt;
public class Order {
protected Coffee coffee;
public Order(Coffee coffee) {
super();
this.coffee = coffee;
}
public static Order of(CoffeeFlavour flavourName, CoffeeLatteArt latteArt, int tableNumber) {
Coffee coffee = CoffeeFactory.makeCoffee(flavourName, latteArt);
System.out.printf("Serving to table '%d'.\n", tableNumber);
System.out.println("------------------------------------------------------");
return new Order(coffee);
}
public Coffee getCoffee() {
return coffee;
}
}
Now the code for CoffeeShop class. CoffeeShop maintains the list of orders served.
xxxxxxxxxx
package org.trishinfotech.flyweight.without;
import java.util.ArrayList;
import org.trishinfotech.flyweight.CoffeeFlavour;
import org.trishinfotech.flyweight.CoffeeLatteArt;
public class CoffeeShop {
private static final ArrayList<Order> orders = new ArrayList<>();
public void takeOrder(CoffeeFlavour flavourName, CoffeeLatteArt latteArt, int tableNumber) {
orders.add(Order.of(flavourName, latteArt, tableNumber));
}
public static int getNumberOfOrders() {
return orders.size();
}
}
And now the Main class to execute and test the output.
x
package org.trishinfotech.flyweight.without;
import org.trishinfotech.flyweight.CoffeeFlavour;
import org.trishinfotech.flyweight.CoffeeLatteArt;
public class Main {
public static void main(String[] args) {
CoffeeShop shop = new CoffeeShop();
shop.takeOrder(CoffeeFlavour.CAPPUCCINO, CoffeeLatteArt.BARISTA_SWAG, 5);
shop.takeOrder(CoffeeFlavour.AFFOGATO, CoffeeLatteArt.FRENCH, 7);
shop.takeOrder(CoffeeFlavour.ESPRESSO, CoffeeLatteArt.FISHBONE, 1);
shop.takeOrder(CoffeeFlavour.LATTE, CoffeeLatteArt.DISNEY, 3);
shop.takeOrder(CoffeeFlavour.CAPPUCCINO, CoffeeLatteArt.CAT, 2);
shop.takeOrder(CoffeeFlavour.ESPRESSO, CoffeeLatteArt.FISHBONE, 8);
shop.takeOrder(CoffeeFlavour.AFFOGATO, CoffeeLatteArt.BARISTA_SWAG, 4);
shop.takeOrder(CoffeeFlavour.CAPPUCCINO, CoffeeLatteArt.DISNEY, 10);
shop.takeOrder(CoffeeFlavour.LATTE, CoffeeLatteArt.LITTLE_BUNNY, 6);
shop.takeOrder(CoffeeFlavour.FRAPPUCCINO, CoffeeLatteArt.DISNEY, 9);
System.out.println("Number of Order Objects: " + CoffeeShop.getNumberOfOrders());
System.out.println("Number of Coffee Objects: " + CoffeeFactory.getNumberOfCoffee());
}
}
And below is the output of the the program:
xxxxxxxxxx
Making 'CAPPUCCINO' with Latte Art 'BARISTA_SWAG'.
Serving to table '5'.
------------------------------------------------------
Making 'AFFOGATO' with Latte Art 'FRENCH'.
Serving to table '7'.
------------------------------------------------------
Making 'ESPRESSO' with Latte Art 'FISHBONE'.
Serving to table '1'.
------------------------------------------------------
Making 'LATTE' with Latte Art 'DISNEY'.
Serving to table '3'.
------------------------------------------------------
Making 'CAPPUCCINO' with Latte Art 'CAT'.
Serving to table '2'.
------------------------------------------------------
Making 'ESPRESSO' with Latte Art 'FISHBONE'.
Serving to table '8'.
------------------------------------------------------
Making 'AFFOGATO' with Latte Art 'BARISTA_SWAG'.
Serving to table '4'.
------------------------------------------------------
Making 'CAPPUCCINO' with Latte Art 'DISNEY'.
Serving to table '10'.
------------------------------------------------------
Making 'LATTE' with Latte Art 'LITTLE_BUNNY'.
Serving to table '6'.
------------------------------------------------------
Making 'FRAPPUCCINO' with Latte Art 'DISNEY'.
Serving to table '9'.
------------------------------------------------------
Number of Order Objects: 10
Number of Coffee Objects: 10
Please notice that the number of Coffee Objects are 10.
Coffee Shop Example by Using Flyweight Design Pattern
Now the same example by using Flyweight Design Pattern. I will try to highlight the differences and also the benefits of using flyweight pattern.
Here's the code for the Coffee class:
x
package org.trishinfotech.flyweight.with;
import org.trishinfotech.flyweight.CoffeeFlavour;
import org.trishinfotech.flyweight.CoffeeLatteArt;
public class Coffee {
protected final CoffeeFlavour flavourName; // intrinsic attribute
protected Coffee(CoffeeFlavour flavourName) {
super();
this.flavourName = flavourName;
}
public CoffeeFlavour getFlavourName() {
return flavourName;
}
protected CoffeeLatteArt applyLatteArt(CoffeeLatteArt latteArt) {
// CoffeeLatteArt latteArt will be act as extrinsic attribute
// So, that will be required in creating and processing of Coffee object as
// method parameters and will not be stored as class members.
return latteArt;
}
}
Hare's the code for CoffeeFactory class:
x
package org.trishinfotech.flyweight.with;
import java.util.WeakHashMap;
import org.trishinfotech.flyweight.CoffeeFlavour;
import org.trishinfotech.flyweight.CoffeeLatteArt;
public class CoffeeFactory {
protected static WeakHashMap<CoffeeFlavour, Coffee> coffeeMap = new WeakHashMap<CoffeeFlavour, Coffee>();
public static Coffee makeCoffee(CoffeeFlavour flavourName, CoffeeLatteArt latteArt) {
Coffee coffee = coffeeMap.get(flavourName);
if (coffee == null) {
coffee = new Coffee(flavourName);
coffeeMap.put(flavourName, coffee);
}
System.out.printf("Making '%s' with Latte Art '%s'.\n", coffee.getFlavourName(),
coffee.applyLatteArt(latteArt));
return coffee;
}
public static int getNumberOfCoffee() {
return coffeeMap.size();
}
}
As you can see that the CoffeeFactory maintains a collection using CoffeeFlavour (intrinsic attribute) as key. This will help us reducing the number of objects created and reusing the existing one.
Here's the code for the Order class:
x
package org.trishinfotech.flyweight.with;
import org.trishinfotech.flyweight.CoffeeFlavour;
import org.trishinfotech.flyweight.CoffeeLatteArt;
public class Order {
protected Coffee coffee;
public Order(Coffee coffee) {
super();
this.coffee = coffee;
}
public static Order of(CoffeeFlavour flavourName, CoffeeLatteArt latteArt, int tableNumber) {
Coffee coffee = CoffeeFactory.makeCoffee(flavourName, latteArt);
System.out.printf("Serving to table '%d'.\n", tableNumber);
System.out.println("------------------------------------------------------");
return new Order(coffee);
}
public Coffee getCoffee() {
return coffee;
}
}
Here's the code for CoffeeShop class:
x
package org.trishinfotech.flyweight.with;
import java.util.ArrayList;
import org.trishinfotech.flyweight.CoffeeFlavour;
import org.trishinfotech.flyweight.CoffeeLatteArt;
public class CoffeeShop {
private static final ArrayList<Order> orders = new ArrayList<>();
public void takeOrder(CoffeeFlavour flavourName, CoffeeLatteArt latteArt, int tableNumber) {
orders.add(Order.of(flavourName, latteArt, tableNumber));
}
public static int getNumberOfOrders() {
return orders.size();
}
}
Now its time to write Main class to execute and test the output:
x
package org.trishinfotech.flyweight.with;
import org.trishinfotech.flyweight.CoffeeFlavour;
import org.trishinfotech.flyweight.CoffeeLatteArt;
public class Main {
public static void main(String[] args) {
CoffeeShop shop = new CoffeeShop();
shop.takeOrder(CoffeeFlavour.CAPPUCCINO, CoffeeLatteArt.BARISTA_SWAG, 5);
shop.takeOrder(CoffeeFlavour.AFFOGATO, CoffeeLatteArt.FRENCH, 7);
shop.takeOrder(CoffeeFlavour.ESPRESSO, CoffeeLatteArt.FISHBONE, 1);
shop.takeOrder(CoffeeFlavour.LATTE, CoffeeLatteArt.DISNEY, 3);
shop.takeOrder(CoffeeFlavour.CAPPUCCINO, CoffeeLatteArt.CAT, 2);
shop.takeOrder(CoffeeFlavour.ESPRESSO, CoffeeLatteArt.FISHBONE, 8);
shop.takeOrder(CoffeeFlavour.AFFOGATO, CoffeeLatteArt.BARISTA_SWAG, 4);
shop.takeOrder(CoffeeFlavour.CAPPUCCINO, CoffeeLatteArt.DISNEY, 10);
shop.takeOrder(CoffeeFlavour.LATTE, CoffeeLatteArt.LITTLE_BUNNY, 6);
shop.takeOrder(CoffeeFlavour.FRAPPUCCINO, CoffeeLatteArt.DISNEY, 9);
System.out.println("------------------------------------------------------------");
System.out.println("Number of Order Objects: " + CoffeeShop.getNumberOfOrders());
System.out.println("Number of Coffee Objects: " + CoffeeFactory.getNumberOfCoffee());
}
}
And below is the output of the program:
xxxxxxxxxx
Making 'CAPPUCCINO' with Latte Art 'BARISTA_SWAG'.
Serving to table '5'.
------------------------------------------------------
Making 'AFFOGATO' with Latte Art 'FRENCH'.
Serving to table '7'.
------------------------------------------------------
Making 'ESPRESSO' with Latte Art 'FISHBONE'.
Serving to table '1'.
------------------------------------------------------
Making 'LATTE' with Latte Art 'DISNEY'.
Serving to table '3'.
------------------------------------------------------
Making 'CAPPUCCINO' with Latte Art 'CAT'.
Serving to table '2'.
------------------------------------------------------
Making 'ESPRESSO' with Latte Art 'FISHBONE'.
Serving to table '8'.
------------------------------------------------------
Making 'AFFOGATO' with Latte Art 'BARISTA_SWAG'.
Serving to table '4'.
------------------------------------------------------
Making 'CAPPUCCINO' with Latte Art 'DISNEY'.
Serving to table '10'.
------------------------------------------------------
Making 'LATTE' with Latte Art 'LITTLE_BUNNY'.
Serving to table '6'.
------------------------------------------------------
Making 'FRAPPUCCINO' with Latte Art 'DISNEY'.
Serving to table '9'.
------------------------------------------------------
------------------------------------------------------------
Number of Order Objects: 10
Number of Coffee Objects: 5
Same output, but the number of Coffee objects is only 5 by using the Flyweight pattern.
That's all! I hope this tutorial helps you understand the Flyweight Design Pattern.
Source code can be found here: Flyweight Design Pattern Sample Code
Liked the article? Please don't forget to press that like button. Happy coding!
Need more articles, please visit my profile: Brijesh Saxena.
Opinions expressed by DZone contributors are their own.
Comments