PHPでデザインパターン【Singleton】


デザインパターンの学習の一環で、各デザインパターンの実装をPHPで行ってみます。
また、振る舞いの確認としてPHPUnitを使用しています。

デザインパターンとは

ソフトウェア開発におけるデザインパターン(型紙(かたがみ)または設計パターン、英: design pattern)とは、過去のソフトウェア設計者が発見し編み出した設計ノウハウを蓄積し、名前をつけ、再利用しやすいように特定の規約に従ってカタログ化したものである。

引用元:デザインパターン (ソフトウェア) – Wikipedia( https://ja.wikipedia.org/wiki/デザインパターン_(ソフトウェア)

つまり過去のエンジニアが解決してきた方法が、パターンとしてまとめられているのがデザインパターンです。

デザインパターンと言ってもいろいろとありますが、最も有名なのはGoF(Gnag of Four)のまとめた23パターンかと思います。

今回はその23パターンのうちの一つである、Singletonパターンについて実装していきます。

Singletonパターンとは

Singleton パターンとは、そのクラスのインスタンスが1つしか生成されないことを保証するデザインパターンのことである。ロケールやルック・アンド・フィールなど、絶対にアプリケーション全体で統一しなければならない仕組みの実装に使用される。

引用元:Singleton パターン – Wikipedia( http://ja.wikipedia.org/wiki/Singleton_パターン

クラスのインスタンスは通常new演算子を使って生成されます。
これが1000回実行されるとすると、1000個のインスタンスが生成されることになり、当然コストのかかる処理になってきます。

そこで、1度生成されたインスタンスを使いまわしたいという場面が出てきます。
また、システム上どうしても1つしかインスタンスを生成したくない場面もあるかと思います。

いずれの場合においても、new演算子を1度しか使わず、そこで生成されたインスタンスを使い回せばいいものですが、当然ミスは起こりえます。

開発者が意識せずとも、あるクラスのインスタンスが1つのみであることを保証するために使われるデザインパターンが、Singletonパターンです。

コード

では、どうしたらインスタンスを1つしか作らないクラスを作れるか、という話になりますが、PHPでは簡単に実装できます。

Singleton.php
<?php
...

class Singleton
{
    private static $singleton;

    // new Singleton()でインスタンスを作成できないよう、アクセス修飾子はprivateにする
    private function __construct()
    {
    }

    // このstaticメソッドで、Singletonクラスのインスタンスを作成する
    public static function getInstance(): Singleton
    {
        if (self::$singleton == null) {
            self::$singleton = new Singleton();
        }

        return self::$singleton;
    }

    // インスタンスのクローンを許可しないようにする
    final function __clone()
    {
        throw new \Exception(sprintf('Clone is not allowed: %s', get_class($this)));
    }
}

動作確認

動作確認用に、以下のテストコードを書いて実行してみます。
確認内容は以下の3つに絞っています。

  • new Singletonでインスタンス化できないようになっているか
  • getInstanceメソッドは、同一のインスタンスを返すか
  • getInstanceで作成したインスタンスをcloneすることによる、別インスタンスの作成ができないか
SingletonTest.php
<?php
...

class SingletonTest extends TestCase
{
    public function testConstructIsNotPublic()
    {
        $reflection = new \ReflectionClass('App\Singleton\Singleton');
        $constructor = $reflection->getConstructor();
        $this->assertFalse($constructor->isPublic());
    }

    public function testGetInstanceReturnsSameInstance()
    {
        $instance_1 = Singleton::getInstance();
        $instance_2 = Singleton::getInstance();

        $this->assertTrue($instance_1 === $instance_2);
    }

    public function testCloneThrowsException()
    {
        $instance_1 = Singleton::getInstance();

        $this->expectException(\Exception::class);
        $this->expectExceptionMessage('Clone is not allowed: App\Singleton\Singleton');
        $instance_2 = clone $instance_1;
    }
}

実行してみます。

# phpunitを実行
./vendor/bin/phpunit tests/singleton --colors=always

# 結果
PHPUnit 6.5.14 by Sebastian Bergmann and contributors.

...                                                                 3 / 3 (100%)

Time: 36 ms, Memory: 4.00MB

OK (3 tests, 4 assertions)

無事テストが通りました!

Singletonパターンに関しては、以上となります。
使いどころは考える必要がありますが、PHPで簡単に実装することができました。

■ コード
https://github.com/masato-d/design-pattern

■ リンク
- PHPでデザインパターン【Iterator】