RitoLabo

Singletonパターン | PHPデザインパターン

  • 公開:
  • カテゴリ: PHP DesignPatterns
  • タグ: PHP,DI,DesignPatterns,Creation,Singleton

Singletonパターンは、インスタンスの生成制御に関するデザインパターン手法の1つで、インスタンスが1つしかない事を保証します。

この、「インスタンスが1つしかない事を保証する」とはどういう事なのか。

Singletonパターンが用いられるのは以下のような状況の時です。

  • そのオブジェクト(クラス)を毎回インスタンス化したとして、複製されたそれらのどれかの状態や値が変更された際に、毎回必ず全てのインスタンスの保持する状態が同一でなければならない状況の時。状態の同期を実装するくらいなら、初めからそのインスタンスが単一であった方が結局シンプルである。
  • インスタンスの生成自体にも負荷がかかるので、1つで済む役割のオブジェクトならそもそも1つだけ存在できれば良い。
  • そのオブジェクトが保有(もしくはロード)する何等かのデータの量がとてつもなく多い場合、いくつもインスタンスが生成されるとパフォーマンスの低下・障害に通じる。

なんだかんだ言ってもあれこれすればインスタンスを1つに限定しなくてもなんとかなる場合もあったりしますが、結局は無駄に複雑化させる事なく、極力シンプルにアプリケーションを構築する為には、Singletonパターンを採用するに至ります。

また、こうした「よからぬ状況」の回避を運用ではなく動作ロジックとして完全に単一である事を実現させる事で、そのインスタンスが1つのみである事を必ず保証し、運用どころか開発時点から事故を無くします。

Singletonパターンのクラス図は以下のようになります。

Singletonパターンのクラス図

その名の通り1つのクラスのみ。極めてシンプルです。

実装例

Singletonパターンを実装したコードは以下のようになります。

app/Singleton/SampleSingleton.php
<?php

namespace App\Singleton;


class SampleSingleton
{
private static $instance;

private $id; // 同一インスタンス検証用のID

private function __construct()
{
$this->id = hash('sha256', time());
}

public static function getInstance()
{
if (empty(self::$instance)) {
self::$instance = new SampleSingleton();
}
return self::$instance;
}

public function getId()
{
return $this->id;
}

public final function __clone()
{
throw new \Exception('This Instance is Not Clone');
}

public final function __wakeup()
{
throw new \Exception('This Instance is Not unserialize');
}

}

上記のコードは最小構成です。実際にはシングルトンを実現した上で、必要なふるまいを定義していきます。

という事で、このクラスが提供している機能は唯一「IDを返す」という事になります。

上記のように動的に生成されたIDが、必ず同じ値を返す為には、やはり単一インスタンス、つまりはシングルトンである必要があります。新しいインスタンスを生成すると新しいIDが生成されてしまうからです。

private function __construct()
{
$this->id = hash('sha256', time());
}

コンストラクタのアクセス修飾子をprivateにする事で、new演算子を用いて新たなインスタンスを作成できなくさせています。

このオブジェクトにアクセスするには、そのオブジェエクトが持つ提供メソッドから取得する必要があります。それがgetInstance()メソッドです。

public static function getInstance()
{
if (empty(self::$instance)) {
self::$instance = new SampleSingleton();
}
return self::$instance;
}

インスタンスが生成されていなければ自身の生成を行い、1度生成されてからはそれのみを返却します。

そしてこのインスタンスに静的にアクセスできるように、getInstance() とメンバ変数 $instancestatic で定義します。いわゆる遅延静的束縛 (Late Static Bindings) です。

private static $instance;

ちなみに、これだけではインスタンスの単一性を保証する事ができません。PHPに限りませんが、インスタンスを複製する方法などあまたあるわけで、とはいえ非常識的な部分は置いておくとして、マジックメソッドに関する生成複製部分は回避しておく必要があります。

そこで以下です。

public final function __clone()
{
throw new \Exception('This Instance is Not Clone');
}

public final function __wakeup()
{
throw new \Exception('This Instance is Not unserialize');
}

マジックメソッドである __clone()__wakeup() は、アクセス修飾子をprivateにするのでも良いですが、ここでは例外を投げています。(どっちでも良いです)つまりは、

  • clone演算子でインスタンスをクローンさせない
  • グローバル関数のunserialize()でインスタンスをアンシリアライズさせない

という事を行っています。そして最後に

public function getId()
{
return $this->id;
}

このクラスの唯一の機能である、IDを返すメソッドです。

ちなみに返却するIDですが、インスタンスを1つだけ生成する際にコンストラクタでIDの生成を行っています。

$this->id = hash('sha256', time());

ではクライアント側からこのクラスを利用します。

<?php

use App\Singleton\SampleSingleton;

$singleton = SampleSingleton::getInstance();

echo $singleton->getId();

$singleton2 = SampleSingleton::getInstance();

echo $singleton2->getId();

結果は以下になります。

5a7f72e01c129081cc23ca2196a76e19de7051341c3b0ffc7d3baba759b15595

5a7f72e01c129081cc23ca2196a76e19de7051341c3b0ffc7d3baba759b15595

同じIDが返されている事が確認できます。インスタンスを生成する毎にIDが生成されるので、この2つが同じIDということは、同一のインスタンスである事が確認できます。

そして、newもcloneもできません。

$new = new SampleSingleton();   // ×

$clone = clone $singleton; // ×

まとめ

Singletonパターンはインスタンスが1つしかない事を保証するものですが、それによってインスタンスの生成を制御し、アプリケーションをシンプルに、そして堅牢に保つ為の手法です。

LaravelなどのPHPフレームワークを使っているなら、コンテナとかキューの処理なんかをイメージするとより理解しやすいかもしれません。例えばキューイングは処理の順番を把握し処理したいのでインスタンスは1つが良い。そういった意味では、DIコンテナなんかは、事実上シングルトンを提供しているようなものです。

ただ反面、何も考えずに用いるとアンチパターンになってしまう事も多々あり、シンプルな構造とは言えその本質を良く理解してからの使用検討が良い手法の1つとも言えます。(密結合/グローバル変数のあれこれ/メンテナンス性の低下など)

まあ依存性注入 (Dependency Injection) がありますしね。

とは言わず、先人の知恵の一つとして、そして己の手数の一つとして理解しておくと良いと思います。