2.9. Flyweight (Mosca)

2.9.1. Objetivo

Minimizar o uso de memória, um Flyweight compartilha memória o quanto for possível com objetos similares. Ele é necessário quando um grande número de objetos são utilizados de forma que não diferem muito em estado. Uma prática comum é manter o estado nas estruturas de dados externos e passá-los para o objeto Flyweight quando necessário.

2.9.2. Diagrama UML

Alt Flyweight UML Diagram

2.9.3. Código

Você também pode encontrar este código no 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. Teste

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}