PHPによるデザインパターン1 Singleton

会社でデザインパターン勉強会を始めたので、その内容を書き残していきたいと思います。
23回に分けて全パターンを理解するのが目標。


参考にした書籍は以下。すでに絶版になってますね。
http://www.amazon.co.jp/PHP%E3%81%AB%E3%82%88%E3%82%8B%E3%83%87%E3%82%B6%E3%82%A4%E3%83%B3%E3%83%91%E3%82%BF%E3%83%BC%E3%83%B3%E5%85%A5%E9%96%80-%E4%B8%8B%E5%B2%A1-%E7%A7%80%E5%B9%B8/dp/4798015164/ref=sr_1_1?ie=UTF8&s=books&qid=1265290118&sr=1-1

Singletonパターン

【目的】
生成するオブジェクトの数を1つに制限する為のパターン。
(GoFパターンの中で唯一、ひとつのクラスで完結するパターンとなる)

Q. なぜ制限する必要があるのか?
A1. クラスのインスタンスはnew演算子で生成されます。
   5回newすれば5つのインスタンスが生成されます。
   しかしインスタンスを生成するのはコストがかかるので、使い回した方が効率が良い場面があります。

A2. 「システム全体で読み込んだデータをキャッシュしておくクラス」等、どうしてもインスタンスを1つしか作りたくない場面があります。


【具体的な実装内容】
インスタンスへのアクセスを制御する


【コード例】

<?php

class SingletonSample
{

    private $_id;

    private static $_instance;

    /**
     * コンストラクタ
     */
    private function __construct() {
        $this->_id = md5(date('ymdhis') . mt_rand());
    }

    /**
     * クラスを生成する為の唯一の窓口
     */
    public static function getInstance()
    {
        if (!isset(self::$_instance)) {
            self::$_instance = new SingletonSample;
        }

        return self::$_instance;
    }

    /**
     * IDを返す
     */
    public function getId()
    {
        return $this->_id;
    }

    /**
     * 複製を禁止する
     */
    public final function __clone()
    {
        throw new RuntimeException('Singletonパターンの為、cloneキーワードの使用は禁止されています。');
    }

}


【呼び出し側のコード例】

<?php
// sample1
    $instance1 = SingletonSample::getInstance();
    $instance2 = SingletonSample::getInstance();

    echo $instance1 === $instance2;    // true が返る

    echo $instance1->getId();
    echo $instance2->getId();    // 上と同じidが返る

// sample2
    $instance = new SingletonSample;    // Fatal Error が発生する

// sample3
    $instance = SingletonSample::getInstance();
    $instance_clone = clone $instance;    // RuntimeException が throw され、Fatal Error が発生する


【clone キーワードについて】
PHP4では、オブジェクトのコピーは値渡しになっていましたが、PHP5では参照渡しに変更されています。

    ex) $copy_obj = $obj;   // PHP4では値渡し。PHP5では参照渡し。

なので、PHP5でオブジェクトの値渡しをする際は、cloneキーワードが用意されています。

    ex) $copy_obj = clone $obj; // 値渡しになる。

なお、このcloneキーワードの挙動はマジックメソッドで上書きできる為、クラス内でオーバーライド可能です。


【もう一つの実装コード例】

<?php
    /**
     * クラスを生成する為の唯一の窓口
     */
    public static function getInstance()
    {
        /** クラスの静的変数として持つか、メソッド内の静的変数として持つかだけの違い */
        static $instance;

        if (!isset($instance)) {
            $instance = new SingletonSample;
        }

        return $instance;
    }


【ZendFramework における Singletonパターンの例】

ZF 1.9.6 Zend_Controller_Front クラスより抜粋

<?php

    /**
     * Constructor
     *
     * Instantiate using {@link getInstance()}; front controller is a singleton
     * object.
     *
     * Instantiates the plugin broker.
     *
     * @return void
     */
    protected function __construct()
    {
        $this->_plugins = new Zend_Controller_Plugin_Broker();
    }

    /**
     * Enforce singleton; disallow cloning
     *
     * @return void
     */
    private function __clone()
    {
    }

    /**
     * Singleton instance
     *
     * @return Zend_Controller_Front
     */
    public static function getInstance()
    {
        if (null === self::$_instance) {
            self::$_instance = new self();
        }

        return self::$_instance;
    }


【PHP4 での実装例】

<?php
class SingletonSample
{
    /**
     * 呼び出し側はこのメソッドを使う!new は使っちゃダメ
     */
    function &getInstance()
    {
        /**  */
        static $singleton;
        if (!isset($singleton)) {
            $singleton = new Singleton();
        }
        return $singleton;
    }

}


【呼び出し側のコード例】

<?php

    $instance1 =& SingletonSample::getInstance();
    $instance2 =& SingletonSample::getInstance();

    echo $instance1 === $instance2;    // true が返る

    $instance = new SingletonSample;    // これを制限する手段は言語仕様上できない為、運用ルールで制限するしかない