PHP7でデザインパターン入門20/23 Flyweightパターン

Flyweight パターンとは

等価なインスタンスを別々の箇所で使用する際に、一つのインスタンスを再利用することによってプログラムを省リソース化することを目的とする。

Flyweight パターン - Wikipedia

flyweighという名がついているように、このデザインパターンは、オブジェクトを「軽く」するためのもの。 ここでいう重さとは、「メモリの使用量」のことを指す。たくさんメモリを使うオブジェクトを「重い」、省メモリのオブジェクトを「軽い」と表現する。

$obj = new Something();

Somethingクラスのインタスタンスを上記で作ることができる。このとき、そのインスタンスを保持するために、メモリが確保される。Somethingクラスのインスタンスがたくさん必要なときに、たくさんのインスタンスを生成してしまうと、メモリの使用量が大きくなる。

Flyweightパターンで使う技法は、一言で言えばインスタンスをできるだけ共有して、無駄に new しない」というもの。

example

以下Flyweghtパターンを使ったサンプルプログラム。 重いインスタンスを作るクラスとして「大きな文字」を表現するクラスを考える。

  • BigChar: 「大きな文字」を表すクラス
  • BigCharFactory: BigCharのインスタンスを共有しながら生成するクラス
  • BigString: BigCharを集めて作った「大きな文字列」を表すクラス
  • Main: テスト動作用

BigCharクラス

BigCharは、ファイルから大きな文字のテキストを読み込んでメモリ上に保持し、printメソッドでそれを表示する。

<?php

class BigChar
{
    const DATA_DIR = __DIR__ . "/Data/";
    private $charname;
    private $fontdata;

    public function __construct(string $charname)
    {
        $this->charname = $charname;
        try {
            $contents = file_get_contents(self::DATA_DIR . "big" . $charname . ".txt");
            if ($contents === false) {
                throw new Exception("the file big{$charname}.txt does not exist.");
            }
            $this->fontdata = $contents;
        } catch (Exception $e) {
            echo "error: " . $e->getMessage();
        }
    }

    public function print()
    {
        echo $this->fontdata;
    }
}

BigCharFactoryクラス

BigCharFactoryクラスは、BigCharクラスのインスタンスを作る。しかし、同じ文字に対応するBigCharクラスのインスタンスすでに作ってあった場合は、それを利用して、新しいインスタンスは作成しない。これまで作ったインスタンスはpoolプロパティに保持する。

<?php

class BigCharFactory
{
    private $pool = [];
    private static $singleton;

    private function __construct()
    {
    }

    public static function getInstance(): BigCharFactory
    {
        if (self::$singleton === null) {
            self::$singleton = new self;
        }
        return self::$singleton;
    }

    public function getBigChar(string $charname): BigChar
    {
        if (isset($this->pool[$charname]) === false) {
            $this->pool[$charname] = new BigChar($charname);
        }
        return $this->pool[$charname];
    }
}

BigStringクラス

BigStringはBigCharを集めて作った「大きな文字列」クラス。bigcharsプロパティはBigCharの配列であり、BigCharのインスタンスを保持する。

<?php

class BigString
{
    private $bigchars;

    public function __construct(string $string)
    {
        $this->bigchars = [];
        $factory = BigCharFactory::getInstance();
        for ($i = 0; $i < strlen($string); $i++) {
            $this->bigchars[$i] = $factory->getBigChar($string[$i]);
        }
    }

    public function print()
    {
        for ($i = 0; $i < count($this->bigchars); $i++) {
            $this->bigchars[$i]->print();
        }
    }
}

Main

<?php
$input = strval(trim(fgets(STDIN)));
if (strlen($input) === 0) {
    echo "[input error]. please input the example below." . PHP_EOL;
    echo "example: 1212123" . PHP_EOL;
    die();
}
$bs = new BigString($input);
$bs->print();

実行結果

$ php Main.php
input digits: 1234
......##........
..######........
......##........
......##........
......##........
......##........
..##########....
................
....######......
..##......##....
..........##....
......####......
....##..........
..##............
..##########....
................
....######......
..##......##....
..........##....
......####......
..........##....
..##......##....
....######......
................
........##......
......####......
....##..##......
..##....##......
..##########....
........##......
......######....
................

ソースコード

github.com

役割

Flyweight

普通に扱うとプログラムが重くなるので共有した方がよいものを表す役。 サンプルコードにおけるBigCharクラス。

FlyweightFactory役

Flyweight役を作る工場の役。この工場を使ってFlyweight役を作ると、インスタンスが共有される。 サンプルコードにおけるBigCharFactoryクラス。

Client役

FlyweightFactory役を使ってFlyweight役を作り出し、それを利用する役。 サンプルコードにおけるBigStringクラス。

まとめ

複数箇所に影響が及ぶ

Flyweightパターンではインスタンスを「共有」することがテーマ。インスタンスを共有するということは、共有しているものを変更すると、参照先全てに影響が及ぶということ。そのため、ほんとうに複数箇所に共有させるべき情報だけをFlyweight役に持たせるべき。

intrinsic と extrinsic を意識する

共有させる情報は、intrinsicな情報である。サンプルコードのBigCharフォントデータは、BigStringのどこに登場しても不変である。そのため、BigCharフォントデータはintrinsicな情報になる。 一方で、共有させない情報は、extrinsicな情報である。置く場所によって変化する情報は共有すべき情報ではない。Flyweightパターンを使う上では、これらを意識して区別する必要がある。

関連パターン

増補改訂版Java言語で学ぶデザインパターン入門

増補改訂版Java言語で学ぶデザインパターン入門