DEV Community

Cover image for 19 - Flyweight
Mangirdas Kazlauskas ๐Ÿš€
Mangirdas Kazlauskas ๐Ÿš€

Posted on • Originally published at Medium

19 - Flyweight

In the last article, I have analysed a creational design pattern that divides the construction of a complex object into several separate steps โ€” Builder. In this article, I would like to analyse and implement a structural design pattern that helps using a huge number of objects in your code that could barely fit into available RAM โ€” it is Flyweight.

Table of Contents

  • What is the Flyweight design pattern?
  • Analysis
  • Implementation
  • Your Contribution

What is the Flyweight design pattern?

Sharing Is Caring

Flyweight belongs to the category of structural design patterns. The intention of this design pattern is described in the GoF book:

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

Letโ€™s take an object-oriented document editor as an example. For document elements like tables, images or figures, separate objects are created. However, for text elements (individual characters) this is not feasible even though it promotes flexibility: characters and any other embedded elements could be treated uniformly, the application could be extended to support new character sets very easily. The reason is simple โ€” limitations of the hardware. Usually, a document contains hundreds of thousands of character objects which would consume a lot of memory and it could lead to unexpected crashes, for instance, when the document is being edited and eventually there would be no memory left for new characters or any other type of embedded objects. How this kind of object-oriented document editors could be implemented, then?

The โ€œsecretโ€ relies on the flyweight objects โ€” shared objects that can be used in multiple contexts simultaneously. But how this could even work? If we reuse the same object, doesnโ€™t that mean that when the object is changed in one place, all the other places are affected, too? Well, the key concept here is the distinction between intrinsic and extrinsic state. The intrinsic state is invariant (context-independent) and therefore can be shared e.g. the code of a character in the used character set. The extrinsic state is variant (context-dependent) and therefore can not be shared e.g. the position of a character in the document. And thatโ€™s the reason why this concept works in object-oriented editors โ€” a separate flyweight object is created for each character in the set (e.g. each letter in the alphabet) which stores the character code as the intrinsic state, while the coordinate positions in the document of that character are passed to the flyweight object as an extrinsic state. As a result, only one flyweight object instance per character could be stored in the memory and shared across different contexts in the document structure. Sharing is caring, right?

Letโ€™s move to the analysis and implementation parts to understand and learn the details about this pattern and how to implement it!

Analysis

The general structure of the Flyweight design pattern looks like this:

Flyweight Class Diagram

  • Flyweight โ€” contains intrinsic state while the extrinsic state is passed to the flyweightโ€™s methods. The object must be shareable (can be used in many different contexts);
  • FlyweightFactory โ€” creates and manages flyweight objects. When a client calls the factory, it checks whether the specific flyweight object exists. If yes, it is simply returned to the client, otherwise, a new instance of the flyweight object is created and then returned;
  • Context โ€” contains the extrinsic state, unique across all original objects;
  • Client โ€” computes or stores the extrinsic state of flyweight(s) and maintains a reference to it/them.

Applicability

The Flyweight design pattern should be used only when your program must support a huge number of objects which barely fit into available RAM. The patternโ€™s effectiveness depends on how and where itโ€™s used. It would be the most useful when:

  • An application uses a large number of objects;
  • The objects drain all available RAM on a target device;
  • The objects contain duplicate states which can be extracted and shared between multiple objects;
  • Many groups of objects could be replaced by a few shared objects once the extrinsic state is removed;
  • The application doesnโ€™t depend on object identity. Since flyweight objects are shared, conceptually distinct objects could be considered as the same object.

Implementation

Let's Make It Real

Sadly, the implementation would not resolve any real-world problem this time, but we will implement a simple representation screen and later investigate how the usage of the Flyweight design pattern reduces memory consumption.

Letโ€™s say, we want to draw our custom background using two different geometric shapes โ€” circles and squares. Also, in the background, we want to put a total of 1000 shapes at random positions. This will be implemented in two different ways:

  • A new shape object would be created for each shape in the background;
  • A flyweight factory would be used which creates a single object per shape.

Later, we will use a profiler tool for Dart Apps โ€” Observatory โ€” to investigate how much memory is used for each of these implementations. Letโ€™s check the class diagram first and then implement the pattern.

Class diagram

The class diagram below shows the implementation of the Flyweight design pattern:

Flyweight Implementation Class Diagram

The ShapeType is an enumerator class defining possible shape types โ€” Circle and Square.

The IPositionedShape is an abstract class that is used as an interface for the specific shape classes:

  • render() โ€” renders the shape โ€” returns the positioned shape widget. Also, the extrinsic state (x and y coordinates) are passed to this method to render the shape in the exact position.

Circle and Square are concrete positioned shape classes that implement the abstract class IPositionedShape. Both of these shapes have their own intrinsic state: circle defines color and diameter properties while square contains color, width properties and a getter height which returns the same value as width.

The ShapeFactory is a simple factory class that creates and returns a specific shape object via the createShape() method by providing the ShapeType.

The ShapeFlyweightFactory is a flyweight factory that contains a map of flyweight objects โ€” shapesMap. When the concrete flyweight is requested via the getShape() method, the flyweight factory checks whether it exists in the map and returns it from there. Otherwise, a new instance of the shape is created using the ShapeFactory and persisted in the map object for further usage.

The FlyweightExample initialises and contains the ShapeFlyweightFactory object. Also, it contains a list of positioned shape โ€” shapesList โ€” which is built using the ShapeFlyweightFactory and flyweight positioned shape objects.

ShapeType

A special kind of class โ€” enumeration โ€” to define different shape types.

shape_type.dart

IPositionedShape

An interface that defines the render() method to be implemented by concrete shape classes. Dart language does not support the interface as a class type, so we define an interface by creating an abstract class and providing a method header (name, return type, parameters) without the default implementation.

ipositioned_shape.dart

Concrete shapes

  • Circle โ€” a specific implementation of the IPositionedShape interface representing the shape of a circle.

circle.dart

  • Square โ€” a specific implementation of the IPositionedShape interface representing the shape of a square.

square.dart

ShapeFactory

A simple factory class that defines the createShape() method to create a concrete shape by providing its type.

shape_factory.dart

ShapeFlyweightFactory

A flyweight factory class that keeps track of all the flyweight objects and creates them if needed.

shape_flyweight_factory.dart

Example

First of all, a markdown file is prepared and provided as a patternโ€™s description:

Flyweight Markdown

FlyweightExample initialises and contains the ShapeFlyweightFactory class object. Also, for demonstration purposes, the ShapeFactory object is initialised here, too. Based on the selected option, either the ShapeFactory or ShapeFlyweightFactory is used to populate a list of IPositionedShape objects which are rendered in the background of the example screen.

flyweight_example.dart

With the ShapeFlyweightFactory, the client โ€” FlyweightExample widget โ€” does not care about the flyweight objectsโ€™ creation or management. IPositionedShape objects are requested from the factory by passing the ShapeType, flyweight factory keeps all the instances of the needed shapes itself, only returns references to them. Hence, only a single instance of a shape object per type could be created and reused when needed.

Flyweight Example

From the example, we could see that either 2 or 1000 shape instances are created to build the screen background. However, to understand what is happening under the hood, we can check the memory consumption using Dart Observatory.

When we access the Markdown screen of the Flyweight design pattern, Circle and Square instances are not created since they are not visible on the screen:

Shapes Not Created

Before rendering the Example screen (without using flyweight factory), a total of 1000 shape instances are created โ€” in this case, 484 circles and 516 squares:

Shapes Without Flyweight

When we use the flyweight factory, only one instance per specific shape is needed which is initiated and then shared (reused) later:

Shapes With Flyweight

One shape instance uses 16 bytes of memory, so when we initiate 1000 shapes, that is ~16kB of memory in total. However, when a flyweight factory is used, only 32 bytes are enough to store all different shape instances โ€” 500 times less memory is needed! Now, if you increase the number of shapes to 1 million, without the flyweight factory you would need ~15.2MB of memory to store them, but with a flyweight factory, the same 32 bytes would be enough.

All of the code changes for the Flyweight design pattern and its example implementation could be found here.

Your Contribution

๐Ÿ’– or ๐Ÿฆ„ this article to show your support and motivate me to write better!
๐Ÿ’ฌ Leave a response to this article by providing your insights, comments or wishes for the next topic.
๐Ÿ“ข Share this article with your friends, colleagues on social media.
โž• Follow me on dev.to or any other social media platform.
โญ Star the Github repository.

Top comments (0)