【PHPデザインパターン】02_Singleton~いくつ作るかを制限する


引用記事

この記事を書くきっかけになったブログです。

記事内の解説やソースコードは、こちらのブログと著者の公開リポジトリを参考にしています。

Do You PHP はてな〜[doyouphp][phpdp]PHPによるデザインパターン入門 - Singleton~いくつ作るかを制限する

概要

  • インスタンスを1つしか生成したくない場合に、生成するオブジェクトの数を1つに制限する。
  • インスタンスへのアクセスを制限する。

構成

処理の流れ

SingletonパターンSingletonクラスによるインスタンスの生成が保証されています。
逆に、他クラスからインスタンスを生成できないように、Singletonクラス内で制限をかけます。

  • 他クラスからインスタンスを作成できないように、Singletonクラスのコンストラクタをprivateで宣言する。
  • 自分自身のインスタンス(Singletonインスタンス)を保持するためのstatic変数を内部に持っている。
  • ただひとつのインスタンスを返すgetInstanceメソッドをstaticで宣言する。
  • クラスをインスタンス化できるのはgetInstanceメソッドだけに限定する。
  • 他クラスからインスタンスを複製できないように、cloneメソッドが呼ばれた場合は例外を返すようにする。

staticで宣言する理由

php.netからの引用です。

クラスプロパティもしくはメソッドをstaticとして宣言することで、クラスのインスタンス化の必要なしにアクセスすることができます。 staticなプロパティは、インスタンス化されたクラスオブジェクトからアクセスすることはできません (staticなメソッドにはアクセスできます)。

staticメソッド内では、矢印演算子(->)$thisが使用できないため、selfを使用してアクセスします。 今回はインスタンス化を行うメソッドと、そのインスタンスが格納されているプロパティに宣言されています。

staticで宣言されたメソッドやプロパティは、インスタンス化が行われていなくても外部から呼び出すことができます。クラス名::メソッド名()のように記述します。これが外部からインスタンスを作成する唯一の方法になります。

さらにコンストラクタがprivateで宣言されているため、外部から勝手にnewすることもできません。

これらの記述により、インスタンスを作成するルートを制限しています。仮に複数のインスタンスを持ちたい場合は、Singletonクラスのメソッド自体を変更するしか方法はありません。

ソースコード

両ファイルは同じ階層に存在します。

my_client.php
<?php
namespace DoYouPhp\PhpDesignPattern\Singleton;

require dirname(__DIR__).'/vendor/autoload.php';

use DoYouPhp\PhpDesignPattern\Singleton\MySingletonSample;

// インスタンスを2回取得する
// getInstanceメソッドはstatic宣言がされているため、selfで呼び出している
$instance1 = MySingletonSample::getInstance();
// 2回めは既存のインスタンス($instance1)が返されているはず!
$instance2 = MySingletonSample::getInstance();

// 2つのインスタンスが同一IDかどうかを確認する
// getIDメソッドはstaticで宣言されていないため、矢印演算子が使用できる
if ($instance1->getID() === $instance2->getID()) {
    echo '$instance1と$instance2は同一のIDです'."<br>"."\n";
} else {
    echo '$instance1と$instance2は違うIDです'."<br>"."\n";
}

// 2つのインスタンスが同一かどうかを確認する
if ($instance1 === $instance2) {
    echo '$instance1と$instance2は同一のインスタンスです'."<br>"."\n";
} else {
    echo '$instance1と$instance2は違うインスタンスです'."<br>"."\n";
}

// cloneを試して複製できないことを確認する
try {
    $instance1_clone = clone $instance1;
} catch (\RuntimeException $e) {
    echo $e->getMessage()."<br>"."\n";
}
MySingletonSample.php
<?php
namespace DoYouPhp\PhpDesignPattern\Singleton;

class MySingletonSample {

    // プロパティ(メンバ変数)の定義
    private $id;

    // インスタンスを保持する変数
    private static $instance;

    // コンストラクタ(オブジェクト生成時にコールされるメソッド)
    // privateで宣言しているため、他のクラスから呼び出すことができない
    // つまり、サブクラスを作成して呼び出すことはできない
    // IDとして、現在のタイムスタンプと乱数の組み合わせからハッシュ値を作成する
    private function __construct() {
        $this->id = md5(time().mt_rand());
    }

    // 唯一のインスタンスを返すためのメソッド
    // インスタンスが存在しない場合のみ、新規にインスタンスを作成する
    // インスタンスが存在する場合は、既存のインスタンスを返す
    // staticで宣言されているため、メソッド中で$thisを使用することができない
    public static function getInstance() {
        if (!isset(self::$instance)) {
            self::$instance = new MySingletonSample();
            echo get_class().'クラスでインスタンスを生成しました'."<br>"."\n";
        } else {
            echo '既に'.get_class().'クラスのインスタンスは存在します'."<br>"."\n";
        }

        return self::$instance;
    }

    // IDを返すメソッド
    public function getID() {
        return $this->id;
    }

     // インスタンスの複製を許可しないようにする
     // finalキーワードで、サブクラスでメソッドを上書きできないようにする
     // RuntimeExceptionは、実行時にだけ発生するようなエラーの際にスローされる
    public final function __clone() {
        throw new \RuntimeException(get_class($this).'クラスでは複数のインスタンスを生成することはできません'."<br>"."\n");
    }
}

おまけ

useでクラスをインポートすると、get_class関数で取得するクラス名がDoYouPhp\PhpDesignPattern\Singleton\MySingletonSampleになるんですね。

require_onceでインポートした時は、MySingletonSampleだけでした。