Flyweight Design Pattern and Immutability: A Perfect Match

Andrew Carter
Andrew Carter
Share

The flyweight pattern is a relatively unknown design pattern in PHP. The fundamental principle behind the flyweight pattern is that memory can be saved by remembering objects after they have been created. Then, if the same objects need to be used again, resources do not have to be wasted recreating them.

Balloon lifting a barbell

You can think of the flyweight pattern as a modification to a conventional object factory. The modification being that rather than just creating new objects all the time, the factory should check to see if it has already created the requested object. If it has, it should return this instance rather than create the object again.

A good use case for the flyweight pattern would be an application that has to load large files. These files would be our flyweight objects.

The Flyweight Object

One important feature of flyweight objects is that they are immutable. This means that they cannot be changed once they have been constructed. This is because our factory can only guarantee that it has remembered the correct object if it can also guarantee that the object it originally created has not been modified.

Below is a very simple example flyweight object for a file. We can tell that it is immutable because the ‘data’ property cannot be changed after the constructor has been called. There is no ‘setData’ method.

class File
{
    private $data;

    public function __construct($filePath)
    {
        // Check to make sure the file exists
        if (!file_exists($filePath)) {
            throw new InvalidArgumentException('File does not exist: '.$filePath);
        }

        $this->data = file_get_contents($filePath);
    }

    public function getData()
    {
        return $this->data;
    }
}

The Flyweight Factory

Our flyweight factory needs to be able to obtain a unique identifier for the flyweight objects that it creates. It can then use this unique identifier to check if it has already created the object that it is being asked for. In many scenarios, the parameter(s) to our factory method will form an appropriate unique identifier for our flyweight; as it should be the case that if we call our factory with the same parameters, it will produce the same result.

In the example below, we use the ‘files’ property as an associative array to store the objects that have been created. The most obvious choice for the unique identifier is the file path, so this will be the key to the elements in the array.

class FileFactory
{
    private $files = array();

    public function getFile($filePath)
    {
        // If the file path isn't in our array, we need to create it
        if (!isset($this->files[$filePath])) {
            $this->files[$filePath] = new File($filePath);
        }

        return $this->files[$filePath];
    }
}

Now we can use the FileFactory class to obtain files without worrying about loading them multiple times!

$factory = new FileFactory;

$myLargeImageA = $factory->getFile('/path/to/my/large/image.png');
$myLargeImageB = $factory->getFile('/path/to/my/large/image.png');

if ($myLargeImageA === $myLargeImageB) {
    echo 'Yay, these are the same object!'.PHP_EOL;
} else {
    echo 'Something went wrong :('.PHP_EOL;
}

A Note on Threading

The flyweight pattern is particularly useful in a multi-threaded environment. The immutability of flyweight objects guarantees that they can be used safely at the same time by multiple threads. This is because there is no chance that two threads will try to change the same object simultaneously.

If you are using the flyweight pattern in an environment that is multi-threaded, you should consider the factory method as a critical section. Locks or similar controls should be used to ensure that two threads do not try to create an object at the same time.

Thinking PHP

Earlier I mentioned that the flyweight pattern is a relatively unknown design pattern in PHP. One of the main reasons for this is that memory usage is not a consideration for many PHP developers. Most of our PHP applications don’t have to do much work and their instances only live for a few milliseconds whilst they process the incoming HTTP request.

If, however, we are using the flyweight pattern in a long running application, we need to be aware of memory leaks. In PHP, memory will remain allocated to an object as long as references to it exist within the scope of the application. As a flyweight factory keeps references to every object it creates, when the number of objects that it could be asked to create is infinite, the application will eventually run out of memory.

The flyweight pattern is best suited to scenarios where the number of objects that the factory will need to create and remember is finite and compatible with the memory constraints on the application. That is to say that the application could not be manipulated into extensively creating and remembering objects until it crashes.

Enumeration with Flyweights

Memory optimization, however, is not the only reason that you would choose to implement the flyweight pattern. The flyweight pattern can also be useful for creating enumeration objects. The Doctrine DBAL is an example of a library that does this. It helps developers to write platform independent code that will work seamlessly with many different storage layers.

In this library, ‘Type’ objects are used to convert back and forth between database values and PHP values. There are different ‘Type’ objects for strings, integers, booleans, floats, arrays, dates and more.

Here is a very simplified version of the abstract ‘Type’ class from the Doctrine DBAL:

abstract class Type
{
    const INTEGER  = 'integer';
    const STRING   = 'string';
    const DATETIME = 'datetime';

    private static $_typeObjects = array();

    private static $_typesMap = array(
        self::INTEGER  => 'Doctrine\DBAL\Types\IntegerType',
        self::STRING   => 'Doctrine\DBAL\Types\StringType',
        self::DATETIME => 'Doctrine\DBAL\Types\DateTimeType',
    );

    public static function getType($name)
    {
        if (!isset(self::$_typeObjects[$name])) {
            if (!isset(self::$_typesMap[$name])) {
                throw DBALException::unknownColumnType($name);
            }

            self::$_typeObjects[$name] = new self::$_typesMap[$name]();
        }

        return self::$_typeObjects[$name];
    }

    // ...
}

As you can see, the flyweight pattern is used to enforce that only one object for each type is created. If developers need to obtain a ‘Type’ object, they can call the ‘getType’ method statically, like this:

$integerType = Type::getType(Type::INTEGER);

Using the flyweight pattern in this manner does help to reduce the memory footprint of the library, but it also helps in other ways too.

Enumeration objects make more sense when the flyweight pattern is used. Without it, strange things happen. Take this for example:

$type1 = Type::getType(Type::INTEGER);
$type2 = Type::getType(Type::INTEGER);

if ($type1 === $type2) {
    echo 'Yay, you used the flyweight pattern!'.PHP_EOL;
} else {
    echo 'Well this is confusing :('.PHP_EOL;
}

Without the flyweight pattern, the above test will not pass. This can be particularly confusing to new, and even experienced developers, on a long day!

Another example of enumeration objects using the flyweight pattern is php-enum by Marc Bennewitz.

Summary

The flyweight pattern is most beneficial in applications where sharing objects can significantly reduce memory use. This pattern is certainly not one that we will commonly encounter in PHP applications, however there are scenarios where it can be useful. Although the pattern is designed to reduce memory use, used incorrectly, memory leaks can occur. Enumeration objects tend to make more sense when the flyweight pattern is used, as there is only ever one instance of an object of each value.

Frequently Asked Questions (FAQs) about Flyweight Design Pattern and Immutability

What is the main advantage of using the Flyweight Design Pattern?

The primary advantage of using the Flyweight Design Pattern is its efficiency in memory usage. This pattern is particularly useful when a program needs to create a large number of similar objects. Instead of creating each object individually, which can consume significant memory, the Flyweight pattern allows the program to share common parts of the object state among multiple objects, thereby reducing memory usage.

How does the Flyweight Design Pattern achieve immutability?

The Flyweight Design Pattern achieves immutability by ensuring that the shared objects are not modifiable once they are created. This is done by making all fields final and only setting their values during the construction of the object. This ensures that once an object is created, its state cannot be changed, thereby making it immutable.

Can the Flyweight Design Pattern be used with mutable objects?

While the Flyweight Design Pattern is typically used with immutable objects, it can also be used with mutable objects. However, this requires careful synchronization to avoid potential issues with concurrent modifications. It’s generally recommended to use the Flyweight pattern with immutable objects to avoid these potential issues.

What is the difference between the Flyweight Design Pattern and other structural design patterns?

The main difference between the Flyweight Design Pattern and other structural design patterns is how they manage object creation and memory usage. While other patterns like the Composite or Decorator patterns focus on how objects are composed or decorated, the Flyweight pattern focuses on sharing object states to reduce memory usage.

How does the Flyweight Design Pattern relate to the concept of object pooling?

The Flyweight Design Pattern is similar to object pooling in that both techniques aim to reuse objects to improve performance and reduce memory usage. However, while object pooling reuses entire objects, the Flyweight pattern reuses only the intrinsic state of objects, allowing different objects to share common parts of their state.

What are some common use cases for the Flyweight Design Pattern?

The Flyweight Design Pattern is commonly used in applications that need to handle a large number of objects. This includes graphical applications, game programming, and text editing applications. For example, in a word processor, each character in a document could be represented as a flyweight object, significantly reducing memory usage.

Are there any drawbacks to using the Flyweight Design Pattern?

While the Flyweight Design Pattern can significantly reduce memory usage, it can also add complexity to the code, especially when used with mutable objects. Additionally, the benefits of this pattern may not be noticeable in applications that do not need to handle a large number of objects.

How does the Flyweight Design Pattern work with garbage collection?

Since the Flyweight Design Pattern reuses objects, it can help reduce the load on the garbage collector by minimizing the creation and destruction of objects. However, care must be taken to ensure that flyweight objects are properly managed to avoid memory leaks.

Can the Flyweight Design Pattern be used in multi-threaded applications?

Yes, the Flyweight Design Pattern can be used in multi-threaded applications. However, when used with mutable objects, careful synchronization is required to avoid concurrent modification issues.

How does the Flyweight Design Pattern handle object identity?

In the Flyweight Design Pattern, object identity is typically not important. Since the pattern reuses objects, multiple references may point to the same object. However, the state of the object is not modifiable, ensuring that the behavior of the object remains consistent across all references.