Efficiently Managing Objects with the Flyweight Design Pattern in Java

In modern software development, optimizing memory usage is paramount, especially for applications running on devices with limited resources or that need to handle a large number of objects. The Flyweight design pattern offers a solution to efficiently manage such objects by sharing common parts. This post explores how the Flyweight pattern can be a valuable addition to your Java development toolkit.

Understanding the Flyweight Design Pattern

The Flyweight pattern, a structural design pattern, is all about using sharing to support a large number of fine-grained objects efficiently. This pattern is particularly useful in scenarios where applications need to create a vast number of objects which could lead to significant memory consumption.

When to Use the Flyweight Pattern

Before implementing the Flyweight pattern, consider the following:

  • The application should require a large number of objects.
  • Object creation should be memory-intensive and potentially slow.
  • Object properties can be categorized into intrinsic and extrinsic, where the extrinsic properties are defined by the client.

The intrinsic properties make each object unique, while the extrinsic properties are set externally by client code to perform various operations.

Implementing Flyweight in Java

To demonstrate the implementation of the Flyweight pattern, we consider a scenario involving shapes such as lines and ovals, which can have attributes like color and width.

Shape Interface and Concrete Classes

First, we define a Shape interface with a method draw to render shapes:

    package com.journaldev.design.flyweight;

    import java.awt.Color;
    import java.awt.Graphics;

    public interface Shape {
        void draw(Graphics g, int x, int y, int width, int height, Color color);
    }

Next, the concrete Line and Oval classes implement the Shape interface. The Line class doesn’t have intrinsic properties, while the Oval class includes a boolean fill property to determine whether the oval should be filled:

    // Line.java
    package com.journaldev.design.flyweight;

    import java.awt.Color;
    import java.awt.Graphics;

    public class Line implements Shape {
        public Line() {
            System.out.println("Creating Line object");
            try {
                Thread.sleep(2000); // Simulate time delay
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        
        @Override
        public void draw(Graphics line, int x1, int y1, int x2, int y2, Color color) {
            line.setColor(color);
            line.drawLine(x1, y1, x2, y2);
        }
    }

Flyweight Factory

The ShapeFactory class uses a HashMap to manage instances of shapes. If a shape of a specified type exists in the map, it is returned; otherwise, a new shape is created, added to the map, and then returned:

    package com.journaldev.design.flyweight;

    import java.util.HashMap;

    public class ShapeFactory {
        private static final HashMap<ShapeType, Shape> shapes = new HashMap<>();

        public static Shape getShape(ShapeType type) {
            Shape shape = shapes.get(type);
            if (shape == null) {
                if (type.equals(ShapeType.OVAL_FILL)) {
                    shape = new Oval(true);
                } else if (type.equals(ShapeType.OVAL_NOFILL)) {
                    shape = new Oval(false);
                } else if (type.equals(ShapeType.LINE)) {
                    shape = a Line();
                }
                shapes.put(type, shape);
            }
            return shape;
        }

        public static enum ShapeType {
            OVAL_FILL, OVAL_NOFILL, LINE;
        }
    }

Example and Usage

The provided example showcases a DrawingClient class, a JFrame application that uses the Flyweight factory to draw different shapes with random attributes:

    // DrawingClient.java
    package com.journaldev.design.flyweight;

    import java.awt.BorderLayout;
    import java.awt.Color;
    import java.awt.Graphics;
    import java.awt.event.ActionEvent;
    import java.awt.event.ActionListener;

    import javax.swing.JButton;
    import javax.swing.JFrame;
    import javax.swing.JPanel;

    public class DrawingClient extends JFrame {
        // Other methods omitted for brevity
        public void actionPerformed(ActionEvent event) {
            Graphics g = panel.getGraphics();
            for (int i = 0; i < 20; ++i) {
                Shape shape = ShapeFactory.getShape(getRandomShape());
                shape.draw(g, getRandomX(), getRandomY(), getRandomWidth(),
                        getRandomHeight(), getRandomColor());
            }
        }
    }

The Flyweight pattern proves beneficial for memory management and performance in Java applications, especially when dealing with a large number of object instances. By understanding and implementing this pattern, developers can ensure more efficient and scalable applications.

Create a Free Account

Register now and get access to our Cloud Services.

Posts you might be interested in: