Flyweight Design Pattern

By Hikmet Cakir

Flyweight Design Pattern

Flyweight is a structural design pattern like Adapter, Composite, and Observer. This design pattern provides the minimization of creating similar objects and reduces memory usage instead of creating too many similar objects.


How To Implement


1. Divide fields of a class that will become a flyweight into two parts:

  • The intrinsic state: the fields that contain unchanging data duplicated across many objects
  • The extrinsic state: the fields that contain contextual data unique to each object


2. The extrinsic fields should be sent as parameters to the relevant method.

3. A factory class must be created to create an object.


Example Implementation

Let’s suppose that we have a combat game that has two character types (Magician, Warrior) and the characters are fighting in the different locations of the map.



package com.hikmetcakir;


public abstract class Character {


    private WeaponType weapon;
    private Location location;


    public Character() {
        // Let's suppose that there are lots of codes in there and these codes consume lots of memory & time
        // To simulate this scenario, These code pieces are added.
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }


    public abstract void attack();


    // Getters & Setters
    public WeaponType getWeapon() {
        return weapon;
    }


    public void setWeapon(WeaponType weapon) {
        this.weapon = weapon;
    }


    public Location getLocation() {
        return location;
    }


    public void setLocation(Location location) {
        this.location = location;
    }
}





package com.hikmetcakir;


import java.util.Locale;


public enum WeaponType {
    SWORD,
    WAND,
    BOW;


    @Override
    public String toString() {
        return this.name().toLowerCase(Locale.ROOT);
    }
}

The character has a weapon and location. Additionally, I added some code pieces to the constructor of the Character class because of simulating problem. As I said before, the flyweight design pattern is used to prevent the creation of too many similar objects. If the character is created again and again, it will take a long time.

package com.hikmetcakir;


public class Warrior extends Character {


    @Override
    public void attack() {
        System.out.println("Warrior is attacking with " + getWeapon() + " in " + getLocation());
    }
}


package com.hikmetcakir;


public class Magician extends Character {


    @Override
    public void attack() {
        System.out.println("Magician is attacking with " + getWeapon() + " in " + getLocation());
    }
}

Basically, every magician and warrior has attack skill in the given position with the given weapon.


package com.hikmetcakir;


public enum ProfessionType {
    WARRIOR,
    MAGICIAN
}


package com.hikmetcakir;


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


public class CharacterFactory {


    private static final Map<ProfessionType, Character>  characterByProfessionType = new HashMap<>();


    public static Character getCharacter(ProfessionType professionType) {
        return characterByProfessionType.computeIfAbsent(professionType, CharacterFactory::createCharacterByProfessionType);
    }


    private static Character createCharacterByProfessionType(ProfessionType professionType) {
        return switch (professionType) {
            case WARRIOR -> new Warrior();
            case MAGICIAN -> new Magician();
        };
    }
}

Characters can be created with the CharacterFactory class by profession type. Additionally, preventing similar object creation is being provided here.

package com.hikmetcakir;


import java.time.Duration;
import java.time.Instant;
import java.util.Random;


public class CombatArea {


    public static void main(String[] args) {
        Instant start = Instant.now();
        for (int i = 0; i < 10; i++) {
            performFlyweightApproach();
//            performClassicApproach();
        }
        Instant end = Instant.now();


        System.out.println("============Result============");
        printPerformanceTimeCalculations(start, end);
    }


    private static void printPerformanceTimeCalculations(Instant start, Instant end) {
        Duration timeElapsed = Duration.between(start, end);
        System.out.println("Time taken: "+ timeElapsed.toMillis()   + " milliseconds");


        double timeAsSecond = timeElapsed.toMillis() / 1000D % 60D;
        System.out.println("Time taken: "+ timeAsSecond + " seconds");
    }


    private static void performFlyweightApproach() {
        Character magician = CharacterFactory.getCharacter(ProfessionType.MAGICIAN);
        magician.setWeapon(WeaponType.WAND);
        magician.setLocation(generateRandomLocation());
        magician.attack();


        Character warrior = CharacterFactory.getCharacter(ProfessionType.WARRIOR);
        warrior.setWeapon(WeaponType.SWORD);
        warrior.setLocation(generateRandomLocation());
        warrior.attack();
    }


    private static void performClassicApproach() {
        Character magician = new Magician();
        magician.setWeapon(WeaponType.WAND);
        magician.setLocation(generateRandomLocation());
        magician.attack();


        Character warrior = new Magician();
        warrior.setWeapon(WeaponType.BOW);
        warrior.setLocation(generateRandomLocation());
        warrior.attack();
    }


    private static Location generateRandomLocation() {
        Random random = new Random();
        int lowerBound = 0;
        int upperBound = 500;
        int randomNumberX = random.nextInt(upperBound - lowerBound) + lowerBound;
        int randomNumberY = random.nextInt(upperBound - lowerBound) + lowerBound;
        return new Location(randomNumberX, randomNumberY);
    }
}

CLI Output:

Magician is attacking with wand in Location(9,121)
Warrior is attacking with sword in Location(137,243)
Magician is attacking with wand in Location(109,381)
Warrior is attacking with sword in Location(420,23)
Magician is attacking with wand in Location(35,210)
Warrior is attacking with sword in Location(290,298)
Magician is attacking with wand in Location(444,127)
Warrior is attacking with sword in Location(480,1)
Magician is attacking with wand in Location(148,0)
Warrior is attacking with sword in Location(93,132)
Magician is attacking with wand in Location(481,431)
Warrior is attacking with sword in Location(278,75)
Magician is attacking with wand in Location(43,128)
Warrior is attacking with sword in Location(456,316)
Magician is attacking with wand in Location(462,174)
Warrior is attacking with sword in Location(434,494)
Magician is attacking with wand in Location(442,338)
Warrior is attacking with sword in Location(298,147)
Magician is attacking with wand in Location(219,206)
Warrior is attacking with sword in Location(394,47)
============Result============
Time taken: 4035 milliseconds
Time taken: 4.035 seconds

I hope everything’s clear for you. If you don’t understand any part, feel free to ask me. Also, I shared the codes I explained and more on GitHub. Additionally, I recommend that you practice with these structures. I coded these structures and these structures’ test scenarios. You can check out to repository.


I used various resources to prepare this essay. I indicated in the following. You can check it out.