Edit Page

Lab 7: The Flyweight Design Pattern

The goal of this lab is to use the flyweight design pattern, a structural design pattern, to solve a real-world problem.

Design patterns are proven solutions to solve recurring design problems to design flexible and reusable object-oriented software; that is, objects that are easier to implement, change, test and reuse. Structural pat­terns deal with the composition of classes or objects while keep­ing them extensible, flex­i­ble and efficient.

The flyweight design pattern is one of the twenty-three well-known Gang of Four (GoF) design patterns. It is classified under the category of structural patterns as it makes the seemingly complex and resource intensive process of creating “lots of objects” more efficient. The flyweight pattern uses sharing to support storing large number of objects efficiently.

In this lab, we will work on the problem of creating a large number of objects that will be used to create an environment for a game.

Cloud Flower Tree Palm

These objects are expected to repeat all over the game environment to enhance the user experience while playing the game. We will see how the flyweight design pattern answers the following questions:

  • How can we reduce the number of stored objects and improve memory storage (RAM) overhead due to the sheer quantity of stored objects?
  • How can we separate the object’s state into intrinsic and extrinsic states?

Video

Objectives

In this lab you will

  1. understand a real-world scenario and choose when to apply the appropriate design pattern.
  2. design and implement the flyweight design pattern.

Requirement and Tools

Getting Started

If your instructor is using GitHub classroom, you will need to accept the assignment using the link below, clone the template repository, and import it as a project into your IDE.

If your instructor is not using GitHub classroom, clone and import the template project at https://github.com/cpit252/lab-07 ↗.

Problem Statement

A developer is working on a game, where it creates a large number of background elements/objects (e.g., clouds, trees, etc.) to create a large set of objects for a game. The developer is planning to release her game on mobile devices but is worried that creating these high quality objects will decrease the performance of her game. Although the game seems relatively simple, creating hundreds of thousands of these objects may impact the overall performance of the game.

The developer started by creating a class called ImageElement, which holds a set of ImageIcon objects for each image in the environment.

ImageElement.java

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import java.awt.*;
import java.io.File;
import java.io.FileNotFoundException;
import java.net.URL;

import javax.swing.ImageIcon;

public class ImageElement {

    private String name;

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

    public String myPosition(int position) {
        return "" + position + " " + this.name + ": " + this;
    }

    public ImageIcon getImageElement() throws FileNotFoundException {
        URL imageURL = this.getClass().getResource(File.separator + "images" + File.separator + this.name);
        ImageIcon imageIcon = new ImageIcon(imageURL);
        if (imageIcon.getImageLoadStatus() != MediaTracker.COMPLETE) {
            throw new FileNotFoundException(String.format("Failed to load image file %s", imageURL.getFile()));
        }
        return imageIcon;
    }
}

Next, she created the main client class that will maintain the extrinsic state and stores references to flyweights. For demonstration purposes, the client app is a Java Swing (GUI) application that will randomly display images in a set of JLabel objects, which will be added to the main JFrame.

Demo.java

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;

import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;

public class Demo {
    private static String[] elements = new String[]{"Tree", "Palm", "Cloud", "Flower"};
    private JPanel panel;
    private JFrame frame;

    public Demo() {
        this.frame = new JFrame();
        this.frame.setTitle("Flyweight images");
        this.frame.setSize(800, 600);
        this.frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        this.panel = new JPanel();
        this.frame.add(panel);
        this.frame.setVisible(true);
    }

    public void addImage(ImageIcon img, int x, int y) {
        JLabel label = new JLabel();
        label.setIcon(img);
        System.out.println("Adding image icon " + img.getDescription());
        System.out.println(String.format("Location (%d,%d)", x, y));
        label.setLocation(x, y);
        this.panel.add(label);
    }

    public static void main(String[] args) {
        Demo demo = new Demo();
        ImageElementsFactory factory = new ImageElementsFactory();

        List<ImageElement> elementList = new ArrayList<ImageElement>();

        System.out.println(
                "Number of flyweights: " +
                        factory.numberOfFlyweights());

        Random r = new Random();
        for (int i = 0; i < 10000; i++) {
            String name = elements[r.nextInt(elements.length)] + ".png";
            ImageElement e = factory.getFlyweight(name);
            elementList.add(e);
            try {
                System.out.println("Image: " + e.getImageElement().toString());
                demo.addImage(e.getImageElement(), 200 * i, 200 * i);
            } catch (FileNotFoundException ex) {
                System.err.println(ex.getMessage());
            }
        }

        int j = 0;

        for (ImageElement l : elementList) {
            System.out.println(l.myPosition(j++));
        }

        System.out.println(
                "Number of flyweights: " +
                        factory.numberOfFlyweights());
    }
}

ImageElementsFactory.java

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
import java.util.Map;
import java.util.HashMap;

public class ImageElementsFactory {

    private Map<String, ImageElement> flyweights = new HashMap<String, ImageElement>();

    public ImageElement getFlyweight(String n) {
        //TODO: return a flyweight if it already exists, otherwise, put it in the map.
 
    }

    public int numberOfFlyweights() {
        // return the size of the HashMap
    }
}

After completing and running the program, it should produce the following output:

Number of flyweights: 0
Image: images/Palm.png
Adding image icon images/Palm.png
Location (0,0)
Image: images/Palm.png
Adding image icon images/Palm.png
Location (200,200)
...
0 Palm.png: ImageElement@11531931
1 Palm.png: ImageElement@11531931
2 Palm.png: ImageElement@11531931
3 Tree.png: ImageElement@5e025e70
4 Palm.png: ImageElement@11531931
5 Tree.png: ImageElement@5e025e70
6 Palm.png: ImageElement@11531931
7 Tree.png: ImageElement@5e025e70
...
9988 Cloud.png: ImageElement@1fbc7afb
9989 Cloud.png: ImageElement@1fbc7afb
9990 Flower.png: ImageElement@45c8e616
9991 Flower.png: ImageElement@45c8e616
9992 Tree.png: ImageElement@11531931
9993 Tree.png: ImageElement@11531931
9994 Tree.png: ImageElement@11531931
9995 Flower.png: ImageElement@45c8e616
9996 Cloud.png: ImageElement@1fbc7afb
9997 Flower.png: ImageElement@45c8e616
9998 Tree.png: ImageElement@11531931
9999 Cloud.png: ImageElement@1fbc7afb
Number of flyweights: 4

Flyweight lab example

Questions:

  1. Complete the implementation of the flyweight design pattern as shown in the code above?
  2. Change the number of created images to hundreds of thousands to experiment with the effects of the flyweight pattern? How many flyweights have been created?
  3. Explain how the flyweight design pattern reduces the number of objects stored in memory compared to storing all objects in memory?

Deliverables and Submission

Extra Task [Optional]

If you are done with this activity, you may enable a continuos integration tool such as CircleCI ↗ to automatically run your JUnit test upon code changes. You may also add more unit tests to increase code coverage. Please embed the badge that shows the status of your build and test (passing/failing) as well as the coverage percentage into your README file (e.g., passing status image and failing status image). Please be sure to fork the repository or push to a remote repository under your own account, so you can enable the integration of CI tools in your own account.

You may refer to the lecture notes from the prerequisite course CPIT-251 on Continuous Integration (CI) and adding a code coverage badge.