Hatena::ブログ(Diary)

気が向いたら RSSフィード

2010-02-04 PHPによるデザインパターン1 Singleton

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

| 22:53 | PHPによるデザインパターン1 Singletonを含むブックマーク

会社でデザインパターン勉強会を始めたので、その内容を書き残していきたいと思います。

23回に分けて全パターンを理解するのが目標。


参考にした書籍は以下。すでに絶版になってますね。

PHPによるデザインパターン入門 | 下岡 秀幸, 道端 良, 畑 勝也 |本 | 通販 | Amazon


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;    // これを制限する手段は言語仕様上できない為、運用ルールで制限するしかない
トラックバック - http://d.hatena.ne.jp/tksthdnr/20100204/1265291624