Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
29
Help us understand the problem. What is going on with this article?

More than 5 years have passed since last update.

@tak-solder

Singletonのデザインパターン

Singleton

GoFが考案した23種類のデザインパターンの一つです。
”Single”とあるように、複数のインスタンスを生成させないためのデザインパターンです。

Singletonを使わない例

共通の例として、クラス内部のPDOでデータベースに接続するクラスを作成しました。

Singletonを使わない場合

<?php

class DB
{
    /**
     * @var PDO
     */
    private $pdo;

    private $conf = [
        'host' => 'localhost',
        'db' => 'test',
        'user' => 'user',
        'password' => 'password',
        'charset' => 'utf8'
    ];

    public function __construct()
    {
        $dsn = "mysql:host={$this->conf['host']};dbname={$this->conf['db']};charset={$this->conf['charset']};";
        $options = [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
        ];

        $this->pdo = new PDO($dsn, $this->conf['user'], $this->conf['password'], $options);
    }

    public function find($id){
        //.....
    }

}

$dbs = [];
for ($i=0; $i<5; $i++) {
    //ループごとにMySQLのコネクションが増える
    $dbs[$i] = new DB();
    $dbs[$i]->find(1);
}

最後の部分でループの中で毎回インスタンスを生成するような処理を入れています。
これでは、最終的に5個のインスタンスが生成されて、MySQLにも5個のコネクションができてしまいます。

Singletonを使ったクラス

<?php

class DB
{
    /**
     * @var PDO
     */
    private $pdo;

    private $conf = [
        'host' => 'localhost',
        'db' => 'test',
        'user' => 'user',
        'password' => 'password',
        'charset' => 'utf8'
    ];

    //コンストラクタをprivateにするのが肝
    private function __construct()
    {
        $dsn = "mysql:host={$this->conf['host']};dbname={$this->conf['db']};charset={$this->conf['charset']};";
        $options = [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
        ];

        $this->pdo = new PDO($dsn, $this->conf['user'], $this->conf['password'], $options);
    }

    //外部からインスタンスを呼び出したい場合のメソッド
    final public static function getInstance()
    {
        static $instance;
        return $instance ?: $instance = new self;
    }

    //クローンを禁止する
    final public function __clone()
    {
        throw new Exception("this instance is singleton class.");
    }

    public function find($id){
        //.....
    }

}

$dbs = [];
for($i = 0; $i < 5; $i++){
    $dbs[$i] = DB::getInstance();
    $dbs[$i]->find(1);
}

ポイント

  1. コンストラクタがprivateメソッドになっている
  2. 外部からはDB::getInstanse()でインスタンスを取得する
  3. インスタンスのcloneの作成を禁止

コンストラクタをprivateメソッドにしていることで、クラスの外からnew DBと新しいインスタンスを生成できなくなっています。
外部からインスタンスを取得するときはDB::getInstanse()を使い、初回時に$instance = new selfでクラス内から自身のインスタンスを作成しています。
また、インスタンスのコピー(クローン)もできないよう、マジックメソッドで例外を発生させるようにしています。

トレイトを使ったクラス

トレイトはPHP5.4から使えるようになりました。

<?php

trait Singleton
{
    //コンストラクタはprotected
    protected function __construct(){}

    //さっきと同じ
    final public static function getInstance()
    {
        static $instance;
        return $instance ?: $instance = new static;
    }

    //さっきと同じ
    final public function __clone()
    {
        throw new Exception("this instance is singleton class.");
    }
}

class DB
{
    //トレイトを使用する宣言
    use Singleton

    /**
     * @var PDO
     */
    private $pdo;

    private $conf = [
        'host' => 'localhost',
        'db' => 'test',
        'user' => 'user',
        'password' => 'password',
        'charset' => 'utf8'
    ];

    //トレイトのコンストラクタを上書き
    protected function __construct()
    {
        $dsn = "mysql:host={$this->conf['host']};dbname={$this->conf['db']};charset={$this->conf['charset']};";
        $options = [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
        ];

        $this->pdo = new PDO($dsn, $this->conf['user'], $this->conf['password'], $options);
    }

    public function find($id){
        //.....
    }

}

$dbs = [];
for($i = 0; $i < 5; $i++){
    $dbs[$i] = DB::getInstance();
    $dbs[$i]->find(1);
}

ポイント

  1. use SingletonでSingletonトレイトの使用を宣言する
  2. Singletonの骨格はトレイトの方に記述
  3. DBクラスでコンストラクタが使えるように、トレイトコンストラクタをprotectedにしている

「Singletonを使ったクラス」をDBクラスとSingletonトレイトの2つに分け、Singletonのパターンを他のクラスでも作成できるよう、トレイトに纏めて、使用するクラスの時にuse Singletonを宣言しています。

29
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
29
Help us understand the problem. What is going on with this article?