PHP
Article
By Andrew Carter

Flyweight Design Pattern and Immutability: A Perfect Match

By Andrew Carter

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.

--ADVERTISEMENT--

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.

Recommended
Sponsors
The most important and interesting stories in tech. Straight to your inbox, daily. Get Versioning.
Login or Create Account to Comment
Login Create Account