Java programming language logo

Flyweight Design Pattern

Flyweight pattern reduces the number of similar object instances.

Flyweight pattern can be used when many similar immutable objects should be created. Instead of creating a new instance every time, it reuses an existing one. It only creates a new instance when no appropriate one available. Thereby performance can be increased, and memory consumption can be decreased.

To achieve this, the object instantiation are delegated to an object producer class. This class caches the created instances. When a client requires an object that exists in the cache, the producer gives back a reference to it instead of creating a new one. If not exists, it creates a new one, puts it into the cache and gives it back.

Flyweight is a structural pattern.

Example

In the next Java example, session objects are created. Session objects are used on severs when users log in. Each logged in user are linked to a session object that stores user data. When there are hundred logged in users, then there are hundred session objects. Every user has different session id and user name, so there are no two identical session instances.

In this example, sessions also store customized user settings, like theme of user interface. Themes are also stored in objects, but there are only a few option of them. When many different session objects are created, not necessary to create new theme instance for each of them. It is not desirable to create many identical objects, those consume too much memory and use resources to instantiate them.

Let’s start with the Session object. It has three fields: a userId that identifies a user, a sessionId that is a unique id generated by the server application, and a Theme object that stores the preferred settings of the current user.


package com.programcodex.designpatterns.flyweight;

public class Session {

    private final String sessionId;
    private final int userId;
    private final Theme theme;

    public Session(int userId, String sessionId, Theme theme) {
        this.userId = userId;
        this.sessionId = sessionId;
        this.theme = theme;
    }

    public Theme getTheme() {
        return theme;
    }
}

A Theme object can have one of the three states: simple, classic or modern. So actually there can be three different versions of it. It has a few fields, and they get values according to the selected theme. Its fields are also final that emphasize they are purposely immutable.


package com.programcodex.designpatterns.flyweight;

public class Theme {

    private final String name; // simple, classic, modern
    private final String layout;
    private final String menu;
    
    public Theme(String name) {
        this.name = name;
        this.layout = name + " layout";
        this.menu = name + " menu";
        
        System.out.println("\nA new Theme object was created:");
        System.out.println(toString());
    }

    @Override
    public String toString() {
        return "Theme name: " + name
                + ", layout type: " + layout
                + ", menu type: " + menu;
    }
}

Next is the SessionCreator object, which produces Session instances. It has a create method that receives a userId and returns with a Session that has a Theme instance. It generates a sessionId and queries the name of the selected theme from the database by the userId.

The queryThemeFromDatabaseByUserId method simulates a database query. Actually it returns with a random theme name.

When a Session object is instantiated in the create method, the getTheme method is called. The getTheme receives the themeName as a parameter. It gets the appropriate Theme object from the themes map by the themeName and returns with it. When the theme doesn’t exist, it creates a new one and puts it into the map for later calls. So this method only creates new Theme instance when there is no appropriate available.


package com.programcodex.designpatterns.flyweight;

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

public class SessionCreator {

    private Map<String, Theme> themes = new HashMap<>();

    public Session create(int userId) {
        String sessionId = UUID.randomUUID().toString();
        String themeName = queryThemeFromDatabaseByUserId(userId);

        return new Session(userId, sessionId, getTheme(themeName));
    }
    
    private Theme getTheme(String themeName) {
        Theme theme = themes.get(themeName);
        if (theme == null) {
            theme = new Theme(themeName);
            themes.put(themeName, theme);
        }
        return theme;
    }

    private String queryThemeFromDatabaseByUserId(int userId) {
        int idx = (int) (Math.random() * 3);
        String[] themeNames = {"simple", "classic", "modern"};
        return themeNames[idx];
    }
}

And we are done. It’s time to run these things.

The for statement creates one hundred Session objects by the SessionCreator class and stores them in the sessions set. This simulates one hundred user log in.

Finally, it prints out the number of the created objects. Luckily there are only three Theme instances and not hundred.


package com.programcodex.designpatterns.flyweight;

import java.util.HashSet;
import java.util.Set;
import java.util.stream.Collectors;

public class TestFlyweight {

    public static void main(String[] args) {
        Set<Session> sessions = new HashSet<>();

        SessionCreator sc = new SessionCreator();
        for (int userId = 0; userId < 100; userId++) {
            Session session = sc.create(userId);
            sessions.add(session);
        }

        System.out.println("\nThe number of Session objects: "
                                        + sessions.size());

        Set<Theme> themes = sessions.stream()
                .map(Session::getTheme)
                .collect(Collectors.toSet());

        System.out.println("The number of Theme objects: "
                                        + themes.size());
    }
}

The output of the above test class:

The Flyweight Design Pattern