Tuesday, February 02, 2010

Practical Php Patterns: Flyweight

This post is part of the Practical Php Pattern series.

The structural pattern of the day is the Flyweight one, which is a technique for the creation of stateful objects used as shared instances.
The pillars of the Flyweight pattern are the following:
  • immutability: changing a Flyweight object in one Client must not affect other Clients that reference it; state change is addressed by substitution with another object, which may be reused. In php (at least php 5) and in most languages objects are passed around with handlers or pointers and so are never copied.
  • no identity: duplicating an object does not provide a clone but only another copy indistinguishable from the source object. There is really no difference between two instances created with the same parameters.
Typical names of a Flyweight class are Currency, DateTime, FontGlyph, Phonenumber, and so on.
Sharing references is useful as there are fewer objects around that may occupy memory and then be garbage collected. The Flyweight pattern allows creating classes for data-intensive entitites and thus promotes homogeneity of the object graph. Flyweight classes could have been modelled instead as arrays or simpler structures (mostly strings), a choice that sometimes impoverish the domain model.
The infrastructure that implements sharing of instances is a factory that maintains a pool of objects and lazy-creates them when asked, recycling previously created instances when the same parameters are passed to its creation methods.
The creator participant may also be a static method since the global state is immutable and testing is still possible. Using a FlyweightFactory is a better approach for extensibility when there is a hierarchy of Flyweights, as the base class would not depend on all its children. Static method is acceptable as very often there is little behavior to stub out from Flyweight objects, which act as beautified data containers.
Participants
  • Client: has a reference to a Flyweight object in some way.
  • FlyweightFactory: creates and maintains all references of Flyweights, recycling them.
  • Flyweight: abstraction of the shared objects.
  • ConcreteFlyweight: (possible) subclasses or implementations of Flyweight.
In an application usually there are two categories of objects: stateless objects (Services) and stateful ones (Entities). Value Objects are similar to Entities as they are stateful objects but without identity, and are usually implemented as Flyweights. User, Article and Group are classic examples of Entities in Domain-Driven Design, while Phonenumber and Money are examples of Value Objects.
The state of an object can be partitioned into two tranches: intrinsic state and extrinsic state.
  • intrinsic state is shared between all Clients. The Flyweight keeps intrinsic state as private fields and in its concrete class name.
  • Extrinsic state is the context where the Flyweight is used, and may not be present. The object must be passed extrinsic state from the Client as a method parameter when needed.
The code sample describes the modelling of a User's nationality as an external class.
    <?php
    /**
     * Flyweight. If there were different behaviors a Flyweight interface
     * would have been extracted and different ConcreteFlyweight implemented,
     * for instance with different constructions of nationality declaration.
     * The fact that a Nationality is treated as a Value Object is arbitrary:
     * if the stored data increase this class should become an Entity.
     */
    class Nationality
    {
        /**
         * @var string  the nation name cannot change
         */
        private $_nationName;
    
        public function __construct($nationName)
        {
            $this->_nationName = $nationName;
        }
    
        public function __toString()
        {
            return $this->_nationName;
        }
    
        /**
         * The person's name is extrinsic state and should be passed instead
         * of stored as a private field. Telling this class the person's name
         * results in a more cohesive design than extracting the nation name:
         * the behavior should be kept near the data it references
         * ($this->_nationName).
         */
        public function getNationalityDeclaration($person)
        {
            return "{$person} is from {$this->_nationName}";
        }
    
        private static $_instances = array();
        /**
         * Implementation of the FlyweightFactory participant as a static method.
         */
        public static function getInstance($name)
        {
            if (!isset(self::$_instances[$name])) {
                self::$_instances[$name] = new self($name);
            }
            return self::$_instances[$name];
        }
    }
    
    /**
     * A Client class, which is a simple bean containing data for the sake of this
     * example.
     */
    class User
    {
        public function getUid()
        {
            return $this->_uid;
        }
    
        public function setUid($uid)
        {
            $this->_uid = $uid;
            return $this;
        }
    
        /**
         * @return Nationality
         */
        public function getNationality()
        {
            return $this->_nation;
        }
    
        public function setNationality($nation)
        {
            $this->_nation = $nation;
            return $this;
        }
    
        public function __toString()
        {
            return "User: #{$this->_uid}. " . $this->_nation->getNationalityDeclaration($this->_uid);
        }
    }
    
    // other Client code
    $user = new User();
    $user->setUid(714673)
         ->setNationality(Nationality::getInstance('Italia'));
    echo $user, "\n";
    // changing a Flyweight means referencing a new instance
    // (which may actually already exist in the FlyweightFactory)
    $user->setNationality(Nationality::getInstance('Australia'));
    echo $user, "\n";
    As another example, you can consider the Doctrine 2 data type system, which is implemented with Flyweight objects that represent different column types with the relative mapping to object fields. Since there are many classes whose metadata references these Flyweight the total load of the Orm on the server is reduced.

    4 comments:

    1. How this pattern is related to the object pool pattern ?

      ReplyDelete
    2. An Object Pool usually contains a fixed number of object of a particular kind (for instance database connections), all indistinguishable from each other, while with Flyweight objects the pool is populated on demand and specific objects are requested. In the former case object are also shared but by only Client at the time; in php given the nature of the language it's uncommon to encounter an object pool.

      ReplyDelete
    3. What's a 'bean'? I'll make something up and pretend it is an acronym for "Business Enterprise Application Node" ;-)

      ReplyDelete
    4. It's a term borrowed from Java:
      http://en.wikipedia.org/wiki/JavaBean
      You know, Java coffee, coffee bean... I think that's the origin of the name. :)

      ReplyDelete