Flyweight Pattern

The pattern allows:

  • to reuse of many objects so, to make the utilization of large numbers of objects more efficient.

Flyweight Pattern


The classical example describing the pattern, which you can find everywhere, is objects graphically representing characters. They don’t comprise font-related information, but contains instead a reference to a flyweight glyph object shared by every instance of the same character in the document.


PHP Example

Stack of log messages can be pretty long. Here in the example identical messages are not duplicated. Instead by request the flyweight shared message object returns.

<?php
/*
 * @category Design Pattern Tutorial
 * @package Flyweight Sample
 * @author Dmitry Sheiko <me@dsheiko.com>
 * @link http://dsheiko.com
 */

/**
 * Flyweight object
 */
class LogEntry
{
    private $_message = null;
    public function  __construct($message)
    {
        $this->_message = $message;
    }
    public function getMessage(LogEntryContext $context)
    {
        return $this->_message . " " . strftime("%b %d, %Y / %H:%M", $context->getTimestamp());
    }
}
class LogEntryContext
{
    private $_timestamp = null;
    public function  __construct($timestamp)
    {
        $this->_timestamp = $timestamp;
    }
    public function  getTimestamp()
    {
        return $this->_timestamp;
    }
}
/**
 * FlyweightFactory object
 */
class LogEntryFactory
{
    private static $_messages = array();
    private static $_callCount = 0;
    public static function make($message)
    {
        $hash = md5($message);
        if (!isset (self::$_messages[$hash])) {
            self::$_messages[$hash] = new LogEntry($message);
        }
        self::$_callCount ++;
        return  self::$_messages[$hash];
    }
    public static function getInstanceCount()
    {
        return count(self::$_messages);
    }
    public static function getRequestCount()
    {
        return self::$_callCount;
    }
}

class Logger
{
    private $_logEntries;
    private $_timeStamps;
    public function  __construct()
    {
        $this->_logEntries = new SplDoublyLinkedList();
        $this->_timeStamps = new SplDoublyLinkedList();
    }
    public function log($message)
    {
        $this->_logEntries->push(LogEntryFactory::make($message));
        $this->_timeStamps->push(new LogEntryContext(time()));
    }
    public function printMessages()
    {
        $len = $this->_logEntries->count();
        for ($i = 0; $i < $len; $i ++) {
            echo $this->_logEntries[$i]->getMessage($this->_timeStamps[$i]), "\n";
        }
    }
}
/**
 * Usage
 */
$logger = new Logger();
$logger->log('Page #1 viewed');
$logger->log('Page #1 updated');
$logger->log('Page #1 viewed');
$logger->log('Page #1 viewed');
$logger->log('Page #1 deleted');
$logger->printMessages();

echo LogEntryFactory::getRequestCount(), " LogEntry instances were requested\n";
echo LogEntryFactory::getInstanceCount(), " LogEntry instances were really created\n";

// Output:
// Page #1 viewed Dec 04, 2011 / 19:04
// Page #1 updated Dec 04, 2011 / 19:04
// Page #1 viewed Dec 04, 2011 / 19:04
// Page #1 viewed Dec 04, 2011 / 19:04
// Page #1 deleted Dec 04, 2011 / 19:04
// 3 LogEntry instances were really created
// 5 LogEntry instances were requested

JS/ES5 Example

/*
 * @category Design Pattern Tutorial
 * @package Flyweight Sample
 * @author Dmitry Sheiko <me@dsheiko.com>
 * @link http://dsheiko.com
 */
(function() {

var ERROR_INUSUFFICIENT_ARG_TYPE = 1,
    ERROR_INUSUFFICIENT_ARG_VALUE = 2,
    ERROR_NODE_IS_UNDEFINED = 3;

var ErrorMap = [];
ErrorMap[ERROR_INUSUFFICIENT_ARG_TYPE] = 'Insufficient argument type';
ErrorMap[ERROR_INUSUFFICIENT_ARG_VALUE] = 'Insufficient argument value';
ErrorMap[ERROR_NODE_IS_UNDEFINED] = 'Node is undefined';

/**
 * Flyweight object
 */
var ErrorLogEntry = function(errCode){
    var _errCode = errCode;
    return {
        getMessage: function(context) {
            return ErrorMap[_errCode] + " " + context.getDate();
        }
    }
};
var ErrorLogEntryContext = function(date){
    var _date = date;
    return {
        getDate: function() {
            return _date;
        }
    }
};

/**
 * FlyweightFactory object
 */
var ErrorLogEntryFactory = (function(){
    var _messages = [], _callCount = 0, _creationCount = 0;
    return {
        make : function(errCode) {
            if (typeof _messages[errCode] === 'undefined') {
                _messages[errCode] = new ErrorLogEntry(errCode);
                _creationCount += 1;
            }
            _callCount += 1;
            return _messages[errCode];
        },
        getInstanceCount: function() {

            return _creationCount;
        },
        getRequestCount: function() {
            return _callCount;
        }
    }
}());

var ErrorLogger = function(){
    var _errCodes = [], _dates = [];
    return {
        log: function(errCode) {
            _errCodes.push(ErrorLogEntryFactory.make(errCode));
            _dates.push(new ErrorLogEntryContext(new Date()));
        },
        printMessages: function() {
            _errCodes.forEach(function(logEntry, inx){
                console.log(logEntry.getMessage(_dates[inx]));
            });
        }
    }
};

/**
 * Usage
 */

var logger = new ErrorLogger();
logger.log(ERROR_INUSUFFICIENT_ARG_TYPE);
logger.log(ERROR_INUSUFFICIENT_ARG_TYPE);
logger.log(ERROR_INUSUFFICIENT_ARG_VALUE);
logger.log(ERROR_INUSUFFICIENT_ARG_TYPE);
logger.log(ERROR_NODE_IS_UNDEFINED);

logger.printMessages();

console.log(ErrorLogEntryFactory.getRequestCount() + " ErrorLogEntry instances were requested");
console.log(ErrorLogEntryFactory.getInstanceCount() + " LogEntry instances were really created");

// Output:
// Insufficient argument type Mon Dec 05 2011 13:02:30 GMT+0100
// Insufficient argument type Mon Dec 05 2011 13:02:30 GMT+0100
// Insufficient argument value Mon Dec 05 2011 13:02:30 GMT+0100
// Insufficient argument type Mon Dec 05 2011 13:02:30 GMT+0100
// Node is undefined Mon Dec 05 2011 13:02:30 GMT+0100
// 5 ErrorLogEntry instances were requested
// 3 LogEntry instances were really created

}());