2.9. Flyweight

2.9.1. Purpose

To minimise memory usage, a Flyweight shares as much as possible memory with similar objects. Itis needed when a large amount of objects is used that don’t differ much in state. A common practice isto hold state in external data structures and pass them to the flyweight object when needed.

2.9.2. UML Diagram

Alt Flyweight UML Diagram

2.9.3. Code

You can also find this code on GitHub

FlyweightInterface.php

  1. <?php
  2.  
  3. namespace DesignPatterns\Structural\Flyweight;
  4.  
  5. interface FlyweightInterface
  6. {
  7. public function render(string $extrinsicState): string;
  8. }

CharacterFlyweight.php

  1. <?php
  2.  
  3. namespace DesignPatterns\Structural\Flyweight;
  4.  
  5. /**
  6. * Implements the flyweight interface and adds storage for intrinsic state, if any.
  7. * Instances of concrete flyweights are shared by means of a factory.
  8. */
  9. class CharacterFlyweight implements FlyweightInterface
  10. {
  11. /**
  12. * Any state stored by the concrete flyweight must be independent of its context.
  13. * For flyweights representing characters, this is usually the corresponding character code.
  14. *
  15. * @var string
  16. */
  17. private $name;
  18.  
  19. public function __construct(string $name)
  20. {
  21. $this->name = $name;
  22. }
  23.  
  24. public function render(string $font): string
  25. {
  26. // Clients supply the context-dependent information that the flyweight needs to draw itself
  27. // For flyweights representing characters, extrinsic state usually contains e.g. the font.
  28.  
  29. return sprintf('Character %s with font %s', $this->name, $font);
  30. }
  31. }

FlyweightFactory.php

  1. <?php
  2.  
  3. namespace DesignPatterns\Structural\Flyweight;
  4.  
  5. /**
  6. * A factory manages shared flyweights. Clients should not instantiate them directly,
  7. * but let the factory take care of returning existing objects or creating new ones.
  8. */
  9. class FlyweightFactory implements \Countable
  10. {
  11. /**
  12. * @var CharacterFlyweight[]
  13. */
  14. private $pool = [];
  15.  
  16. public function get(string $name): CharacterFlyweight
  17. {
  18. if (!isset($this->pool[$name])) {
  19. $this->pool[$name] = new CharacterFlyweight($name);
  20. }
  21.  
  22. return $this->pool[$name];
  23. }
  24.  
  25. public function count(): int
  26. {
  27. return count($this->pool);
  28. }
  29. }

2.9.4. Test

Tests/FlyweightTest.php

  1. <?php
  2.  
  3. namespace DesignPatterns\Structural\Flyweight\Tests;
  4.  
  5. use DesignPatterns\Structural\Flyweight\FlyweightFactory;
  6. use PHPUnit\Framework\TestCase;
  7.  
  8. class FlyweightTest extends TestCase
  9. {
  10. private $characters = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k',
  11. 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'];
  12. private $fonts = ['Arial', 'Times New Roman', 'Verdana', 'Helvetica'];
  13.  
  14. public function testFlyweight()
  15. {
  16. $factory = new FlyweightFactory();
  17.  
  18. foreach ($this->characters as $char) {
  19. foreach ($this->fonts as $font) {
  20. $flyweight = $factory->get($char);
  21. $rendered = $flyweight->render($font);
  22.  
  23. $this->assertEquals(sprintf('Character %s with font %s', $char, $font), $rendered);
  24. }
  25. }
  26.  
  27. // Flyweight pattern ensures that instances are shared
  28. // instead of having hundreds of thousands of individual objects
  29. // there must be one instance for every char that has been reused for displaying in different fonts
  30. $this->assertCount(count($this->characters), $factory);
  31. }
  32. }