Guide to Design Patterns – Flyweight Pattern

Design Patterns - Flyweight Pattern

This week we’ll continue our exploration of design patterns and look at a structural pattern, the Flyweight pattern.

The Gang of Four book has a very simple definition for the Flyweight pattern:

“Use sharing to support large numbers of fine-grained objects efficiently.”

Understanding the Flyweight Pattern

Let’s say that we have an application that needs a large number of objects of the same type. If each of those objects has a large amount of internal state (instance fields), then creating many of them will use a proportionally large amount of memory.

If we look carefully at each object’s state, we might find that some of the fields are common, and could potentially be shared among the set of objects. We would then move the common shareable state into a flyweight object.

The internal state of an object can be split into two parts:

  • Intrinsic (shared) data: This is stored in the flyweight object. The data is independent of the flyweight’s context and can be shared. We can replace all of the objects having the same intrinsic data with a single immutable flyweight object.
  • Extrinsic (unshared) data: This is data that depends on the flyweight’s context and cannot be shared. Client code stores and computes extrinsic data, and passes that data to the flyweight when it is needed.

Because the same flyweight object can be used in different contexts, we must ensure that its state can’t be modified. A flyweight should be immutable and initialize its state in a constructor. It shouldn’t expose any public fields or setters to other objects.

The Flyweight pattern can usually be recognized by an object creation method returning cached objects instead of creating new objects.

An example of this from the standard Java API libraries is the Integer class. Internally it contains a cache of values in the range -128 to 127. These cached values are returned by the Integer.valueOf(int) method. The ByteShort and Long classes have the same behaviour. The Boolean class has two flyweight objects FALSE and TRUE. The BigDecimal and BigInteger classes have three flyweight objects each: ZEROONE and TEN.

Flyweight Pattern: Design Example

Let’s go back to our first person shooter game. We’ve already created many types of monsters, such as zombies, vampires, werewolves and the like. We occasionally create hordes of these monsters to attack players. Hordes of monsters can use a lot of memory, so we’d like a way to share common state between monsters of particular types.

If we think about the internal state of each monster, we would need to model its position on the map, its current damage/health, the set of images that we’d use to draw the monster, and other relevant data.

public class Monster {
   // instance fields to model state 
   private int x, y, z;       // map position/co-ordinates 
   private int damage;        // damage/health value 
   private SpriteSet sprites; // images to draw the monster 
   private String typeName;   // monster type name 
   // other code 
}

Flyweight Class

Each monster will have its own position and damage/health, but all monsters of the same type (ZombieVampireWerewolf, etc.) will contain duplicate data (type name, images, sprites, etc.). We can store these duplicate values inside separate flyweight objects, e.g. a MonsterType class. We will then reference a single shared flyweight object, instead of storing the same duplicate data in the hordes of Monster objects.

Let’s move the shared data out of the Monster class and into the new MonsterType class.

// Contains state that is shared between Monsters 
public class MonsterType {
   // instance fields 
   private String typeName;    // monster type name 
   private SpriteSet sprites;  // images used to draw the monster 
   // constructors and other code 
}

Flyweight Factory

We usually create a factory method that manages a pool of existing flyweight objects. When a client requests a flyweight object for the first time, the factory instantiates the flyweight, initializes it with the required intrinsic data, adds it to the pool, and then returns it to the client. With subsequent requests, the factory retrieves the existing flyweight from the pool and returns it to the client.

The following is code for a MonsterType factory that instantiates and caches the flyweight objects for the different monsters:

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

// The factory creates and pools flyweight objects 
public class MonsterTypeFactory {

   private static Map cache = new HashMap<>();

   public static MonsterType getMonsterType(String key) {

      // If key is in the cache, return appropriate flyweight. 
      if (cache.containsKey(key)) {
         return cache.get(key);
      }
      // If the key is not in the cache, create the  
      // flyweight, put it into the cache, and return it. 
      MonsterType monsterType;
      switch (key)
      {
         case "Vampire" :
            monsterType = new MonsterType("Vampire", Vampire.SPRITE_SET);
            break;
         case "Zombie" :
            monsterType = new MonsterType("Zombie", Zombie.SPRITE_SET);
            break;
         default:
            throw new IllegalArgumentException("Unsupported monster type.");
      }
      cache.put(key, monsterType);
      return monsterType;
   }
}

This factory looks very much like the CreatureFactory we created in the earlier post on the Simple Factory design pattern. The difference here is that we also cache the objects.

Context Class

The Flyweight pattern suggests that we shouldn’t store the extrinsic state inside an object, but rather pass the state to any methods that need it. We will then need a much smaller number of objects since they only differ in the intrinsic state (the flyweight object). The intrinsic state generally has fewer variations than the extrinsic state.

public abstract class Monster {
   // Shared intrinsic state stored in the flyweight 
   private MonsterType type; 

   // Unique extrinsic state is passed in by client code 
   public abstract void move(int newX ,int newY, int newZ);
} 

An alternative solution is to create a separate context class that stores both the extrinsic state and a reference to the flyweight object:

// Context class contains both intrinsic and extrinsic state 
public class Monster {

   // instance fields for extrinsic state 
   private int x, y, z;      // map position/co-ordinates 
   private int damage;       // damage/health value 

   // reference to intrinsic flyweight state 
   private MonsterType type; 

   public void move(int newX ,int newY, int newZ) {
      x = newX; y = newY; z = newZ;
      // relevant code 
   }

   // other code 
}

We will still need as many of these contextual objects as we had originally. But the objects are much smaller than before. The memory-hungry fields have been moved to just a few flyweight objects. Now if we create a hundred Monster objects, they will all reuse a single MonsterType flyweight object, instead of having a hundred extra copies of duplicated data in memory.

Client Code

The client code is responsible for maintaining and changing the state of the individual objects. The client calculates the extrinsic state (position, movement, health, etc.) of the monsters, and passes it to the relevant methods.

A code snippet follows:

// in client code 

Monster monster[] = new Monster[] {
      new Monster(MonsterTypeFactory.getMonsterType("Vampire") ),
      new Monster(MonsterTypeFactory.getMonsterType("Werewolf")),
      new Monster(MonsterTypeFactory.getMonsterType("Zombie")  )
      };

// client calculates new positions 
monster[0].move(50, 30, 20);
monster[1].move(30, 20,  0);
monster[2].move(10, 10,  0);

// more code... 

Pros and Cons of the Flyweight Pattern

The Flyweight pattern isn’t a very commonly used pattern nowadays because computers have become more powerful and RAM has become vastly cheaper. But when we’re dealing with large-scale systems, it will help minimise memory usage and save system resources.

What’s Next?

In the weeks ahead, we’ll continue examining some of the more useful design patterns. Stay tuned!

As always, please share your comments and questions.

Leave a Comment

Your email address will not be published. Required fields are marked *

Code like a Java Guru!

Thank You

We're Excited!

Thank you for completing the form. We're excited that you have chosen to contact us about training. We will process the information as soon as we can, and we will do our best to contact you within 1 working day. (Please note that our offices are closed over weekends and public holidays.)

Don't Worry

Our privacy policy ensures your data is safe: Incus Data does not sell or otherwise distribute email addresses. We will not divulge your personal information to anyone unless specifically authorised by you.

If you need any further information, please contact us on tel: (27) 12-666-2020 or email info@incusdata.com

How can we help you?

Let us contact you about your training requirements. Just fill in a few details, and we’ll get right back to you.

Your Java tip is on its way!

Check that incusdata.com is an approved sender, so that your Java tips don’t land up in the spam folder.

Our privacy policy means your data is safe. You can unsubscribe from these tips at any time.