Flyweight Design Pattern with Java

There are some situations in software development in which we need to improve performance. This is possible with the use of Cache. Imagine a lot of the same objects being created and wasting memory. The Flyweight pattern was created to avoid this problem and optimize performance.

Flyweight_diagram.PNG

Get the Design_Patterns_Saga_GitHub_Source_Code

1 – POJO: This is the object we will keep in the Cache.

class Product {
  private final String name;

  public Product(String name) {
    this.name = name;
  }

  public String toString() {
    return name;
  }
}

2 – Factory (Cache): Basically this class is responsible to create the caches.

lookup(String productName): It checks if the product is contained in the map. If it is, we will return the product. If not, we will put the product on the map and return the product.

public class Portfolio {
  private Map<String, Product> products = new HashMap<String, Product>();

  /**
  * Factory method Pattern
  */
  public Product lookup(String productName) {
    if (!products.containsKey(productName)) {
      products.put(productName, new Product(productName));
    }
    return products.get(productName);
  }

  public int totalProductsMade() {
    return products.size();
  }
}

3 – Object composer: It just composes the information from the product to execute the order.

public class Order {
  private final int orderNumber;
  private final Product product;

  Order(int orderNumber, Product product) {
    this.orderNumber = orderNumber;
    this.product = product;
  }

  void processOrder() {
    System.out.println("Ordering " + product + " for order number " + orderNumber);
  }
}

4 – Executor: Here is where the action happens.

void executeOrder(String productName, int orderNumber): It orchestrates the input in the map. If the product is on the map already, it will return the same object. If not, it will put the new information in the map and return it.

void process(): It will process all the orders and remove every product.

int getTotalProducts(): It will get the total of the registered products.

public class InventorySystem {

  private final Portfolio portfolio = new Portfolio();
  private final List<Order> orders = new CopyOnWriteArrayList<Order>();

  void executeOrder(String productName, int orderNumber) {
    Product product = portfolio.lookup(productName);
    Order order = new Order(orderNumber, product);
    orders.add(order);
  }

  void process() {
    for (Order order : orders) {
      order.processOrder();
      orders.remove(order);
    }
  }

  int getTotalProducts() {
    return portfolio.totalProductsMade();
  }
}

5 – Unit Tests: Let’s see if the cache is working!

public void flyweightTest(): At first we will invoke the executeOrder method from the InventorySystem and process it. When the products are added it automatically removes the duplication. In the end, we check if there are 3 products, and the test passes.

public void flyweightJavaApiExampleTest(): There is something curious about Wrappers of Numbers in Java. When we use an Integer between -127 and 128 there is a Cache that maintains those numbers in order to avoid wasted memory.

public class FlyweightTest {

  @Test
  public void flyweightTest() {
    InventorySystem inventory = new InventorySystem();

    inventory.executeOrder("AlienWare laptop", 2500);
    inventory.executeOrder("SkullCandy HeadPhones", 150);
    inventory.executeOrder("Playstation 5", 500);
    inventory.executeOrder("SkullCandy HeadPhones", 130);
    inventory.executeOrder("AlienWare laptop", 3000);
    inventory.executeOrder("Playstation 5", 600);

    inventory.process();

    Assert.assertEquals(3, inventory.getTotalProducts());
  }

  @Test
  public void flyweightJavaApiExampleTest() {
    Integer firstInt = Integer.valueOf(5);
    Integer secondInt = Integer.valueOf(5);
    Integer thirdInt = Integer.valueOf(10);

    Assert.assertTrue(firstInt == secondInt);
    Assert.assertFalse(secondInt == thirdInt);
  }

}

Summary of actions:

  1. Created the POJO to keep in the Cache.
  2. Created the auxiliary classes to make the Flyweight pattern work.
  3. Created the map from the POJO to get rid of duplication.
  4. Map created from the POJO without duplications.

To practice the Flyweight pattern you can create another POJO and your own implementation to avoid duplication. Practice TDD with this example!

Written by
Rafael del Nero
Join the discussion

2 comments
  • Hi there,
    methods in InventorySystem class should be public, otherwise, we cannot access them from test/java folder

    • Hi Pavel! Good catch!

      Thanks to your comment I learned that when we use packages in the same structure in “main” and “test”, Java recognizes the package as the same and that’s the reason there is no error in this case.

      It makes sense because the package path is the same for main/java/ and test/java:
      package com.designpatternsaga.flyweight;

      Keep up the great work and keep the code on!