Edited at

シングルトンの抽象化クラス&トレイトを作成する

More than 3 years have passed since last update.

シングルトンパターンの抽象化クラスを作成してみます。まずはシングルトンパターンのおさらいから。


 クラスのインスタンスはnew演算子を使って生成されます。たとえば、5回new演算子を使った場合、5つのインスタンスが生成されます。当然、1000回実行すると1000個のインスタンスが生成されます。

 しかし、インスタンスを生成するという処理は、コストがかかる処理です。オブジェクトの使いまわしをしないで毎回newするのは、大きなコストがかかってしまうことを意味します。

 また、「どうしてもインスタンスを1つしか生成したくない」といった場面も出てきます。たとえば、システムの設定を表現するクラスや、システム全体で一度読み込んだデータをキャッシュしておくクラスなどです。

 この場合、プログラミングする際に注意深くnew演算子を使うことで、1つしかインスタンスを生成させないようにすることもできます。しかし、それは「保証」されたものではありません。当然、何らかのミスや、それを知らない開発者が後からどんどんnewしていってしまうことも考えられます。

 開発者が意識しなくても、あるクラスのインスタンスが1つしか存在しないことを「保証する」ために使われるデザインパターン…それがSingletonパターンです。



Singleton.php(呼び出し先)

class Singleton

{
// 静的メソッドから呼び出せるのは静的プロパティだけ
private static $instance;

// privateにすることで他のクラスからnewできない
private function __construct()
{
}

// staticにすることでnewしなくてもメソッドが呼べるようにする
public static function getInstance()
{
if (!self::$instance) self::$instance = new Singleton;
return self::$instance;
}

// finalにすることで子クラスから上書きできないようにする
final function __clone()
{
throw new \Exception('Clone is not allowed against' . get_class($this));
}
}



index.php(呼び出し元)


$singleton = Singleton::getInstance();

複数存在しうるEntityなどとは違って、たとえばreopsitoryなどはDBを取り扱う道具ですので、ひとつあれば十分なわけです。じゃぁ各repositoryにgetInstance()を書くかっていわれたら、抽象クラスをつくりたくなりますよね。


repository.php

abstract class Repository 

{
private static $instance = [];

private function __construct()
{
}

public static function getInstance()
{
$class = get_called_class();
if (!isset(self::$instance[$class])) self::$instance[$class] = new $class;

return self::$instance[$class];
}

public final function __clone()
{
throw new \Exception('Clone is not allowed against' . get_class($this));
}
}


テストしてみます。


testRepository.php

require_once('repository.php');

class TestRepository extends Repository
{
}

$test = TestRepository::getInstance();
var_dump($test); // object(TestRepository)[1]


できました。

(追記)php5.4以上ではtraitを使ったほうがスマートじゃないか、という指摘を頂いたので、


repository.php

trait Repository 

{
private static $instance = [];

private function __construct()
{
}

public static function getInstance()
{
$class = get_called_class();
if (!isset(self::$instance[$class])) self::$instance[$class] = new $class;

return self::$instance[$class];
}

public final function __clone()
{
throw new \Exception('Clone is not allowed against' . get_class($this));
}
}



testRepository.php

require_once('repository.php');

class TestRepository
{
use Repository;
}

$test = TestRepository::getInstance();
var_dump($test); // object(TestRepository)[1]


できました。