2.9. Приспособленец (Flyweight)

2.9.1. Назначение

Для уменьшения использования памяти Приспособленец разделяет как можно больше памяти между аналогичными объектами. Это необходимо, когда используется большое количество объектов, состояние которых не сильно отличается. Обычной практикой является хранение состояния во внешних структурах и передавать их в объект-приспособленец, когда необходимо.

2.9.2. Диаграмма UML

Alt Flyweight UML Diagram

2.9.3. Код

Вы можете найти этот код на GitHub

Text.php

 1<?php
 2
 3declare(strict_types=1);
 4
 5namespace DesignPatterns\Structural\Flyweight;
 6
 7/**
 8 * This is the interface that all flyweights need to implement
 9 */
10interface Text
11{
12    public function render(string $extrinsicState): string;
13}

Word.php

 1<?php
 2
 3namespace DesignPatterns\Structural\Flyweight;
 4
 5class Word implements Text
 6{
 7    public function __construct(private string $name)
 8    {
 9    }
10
11    public function render(string $extrinsicState): string
12    {
13        return sprintf('Word %s with font %s', $this->name, $extrinsicState);
14    }
15}

Character.php

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

TextFactory.php

 1<?php
 2
 3declare(strict_types=1);
 4
 5namespace DesignPatterns\Structural\Flyweight;
 6
 7use Countable;
 8
 9/**
10 * A factory manages shared flyweights. Clients should not instantiate them directly,
11 * but let the factory take care of returning existing objects or creating new ones.
12 */
13class TextFactory implements Countable
14{
15    /**
16     * @var Text[]
17     */
18    private array $charPool = [];
19
20    public function get(string $name): Text
21    {
22        if (!isset($this->charPool[$name])) {
23            $this->charPool[$name] = $this->create($name);
24        }
25
26        return $this->charPool[$name];
27    }
28
29    private function create(string $name): Text
30    {
31        if (strlen($name) == 1) {
32            return new Character($name);
33        }
34        return new Word($name);
35    }
36
37    public function count(): int
38    {
39        return count($this->charPool);
40    }
41}

2.9.4. Тест

Tests/FlyweightTest.php

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