享元模式(Flyweight)

目的

为了节约内存的使用,享元模式会尽量使类似的对象共享内存。在大量类似对象被使用的情况中这是十分必要的。常用做法是在外部数据结构中保存类似对象的状态,并在需要时将他们传递给享元对象。

UML 图

r5QLueDXC8.png

代码

你也可以在 GitHub 上查看此代码

FlyweightInterface.php

  1. <?php
  2. namespace DesignPatterns\Structural\Flyweight;
  3. /**
  4. * 创建享元接口 FlyweightInterface 。
  5. */
  6. interface FlyweightInterface
  7. {
  8. /**
  9. * 创建传递函数。
  10. * 返回字符串格式数据。
  11. */
  12. public function render(string $extrinsicState): string;
  13. }

CharacterFlyweight.php

  1. <?php
  2. namespace DesignPatterns\Structural\Flyweight;
  3. /**
  4. * 假如可以的话,实现享元接口并增加内存存储内部状态。
  5. * 具体的享元实例被工厂类的方法共享。
  6. */
  7. class CharacterFlyweight implements FlyweightInterface
  8. {
  9. /**
  10. * 任何具体的享元对象存储的状态必须独立于其运行环境。
  11. * 享元对象呈现的特点,往往就是对应的编码的特点。
  12. *
  13. * @var string
  14. */
  15. private $name;
  16. /**
  17. * 输入一个字符串对象 $name。
  18. */
  19. public function __construct(string $name)
  20. {
  21. $this->name = $name;
  22. }
  23. /**
  24. * 实现 FlyweightInterface 中的传递方法 render() 。
  25. */
  26. public function render(string $font): string
  27. {
  28. // 享元对象需要客户端提供环境依赖信息来自我定制。
  29. // 外在状态经常包含享元对象呈现的特点,例如字符。
  30. return sprintf('Character %s with font %s', $this->name, $font);
  31. }
  32. }

FlyweightFactory.php

  1. <?php
  2. namespace DesignPatterns\Structural\Flyweight;
  3. /**
  4. * 工厂类会管理分享享元类,客户端不应该直接将他们实例化。
  5. * 但可以让工厂类负责返回现有的对象或创建新的对象。
  6. */
  7. class FlyweightFactory implements \Countable
  8. {
  9. /**
  10. * @var CharacterFlyweight[]
  11. * 定义享元特征数组。
  12. * 用于存储不同的享元特征。
  13. */
  14. private $pool = [];
  15. /**
  16. * 输入字符串格式数据 $name。
  17. * 返回 CharacterFlyweight 对象。
  18. */
  19. public function get(string $name): CharacterFlyweight
  20. {
  21. if (!isset($this->pool[$name])) {
  22. $this->pool[$name] = new CharacterFlyweight($name);
  23. }
  24. return $this->pool[$name];
  25. }
  26. /**
  27. * 返回享元特征个数。
  28. */
  29. public function count(): int
  30. {
  31. return count($this->pool);
  32. }
  33. }

测试

Tests/FlyweightTest.php

  1. <?php
  2. namespace DesignPatterns\Structural\Flyweight\Tests;
  3. use DesignPatterns\Structural\Flyweight\FlyweightFactory;
  4. use PHPUnit\Framework\TestCase;
  5. /**
  6. * 创建自动化测试单元 FlyweightTest 。
  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. public function testFlyweight()
  14. {
  15. $factory = new FlyweightFactory();
  16. foreach ($this->characters as $char) {
  17. foreach ($this->fonts as $font) {
  18. $flyweight = $factory->get($char);
  19. $rendered = $flyweight->render($font);
  20. $this->assertEquals(sprintf('Character %s with font %s', $char, $font), $rendered);
  21. }
  22. }
  23. // 享元模式会保证实例被分享。
  24. // 相比拥有成百上千的私有对象,
  25. // 必须要有一个实例代表所有被重复使用来显示不同单词的字符。
  26. $this->assertCount(count($this->characters), $factory);
  27. }
  28. }