シングルトンパターンの抽象化クラスを作成してみます。まずはシングルトンパターンのおさらいから。
クラスのインスタンスはnew演算子を使って生成されます。たとえば、5回new演算子を使った場合、5つのインスタンスが生成されます。当然、1000回実行すると1000個のインスタンスが生成されます。
しかし、インスタンスを生成するという処理は、コストがかかる処理です。オブジェクトの使いまわしをしないで毎回newするのは、大きなコストがかかってしまうことを意味します。
また、「どうしてもインスタンスを1つしか生成したくない」といった場面も出てきます。たとえば、システムの設定を表現するクラスや、システム全体で一度読み込んだデータをキャッシュしておくクラスなどです。
この場合、プログラミングする際に注意深くnew演算子を使うことで、1つしかインスタンスを生成させないようにすることもできます。しかし、それは「保証」されたものではありません。当然、何らかのミスや、それを知らない開発者が後からどんどんnewしていってしまうことも考えられます。
開発者が意識しなくても、あるクラスのインスタンスが1つしか存在しないことを「保証する」ために使われるデザインパターン…それがSingletonパターンです。
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));
}
}
$singleton = Singleton::getInstance();
複数存在しうるEntityなどとは違って、たとえばreopsitoryなどはDBを取り扱う道具ですので、ひとつあれば十分なわけです。じゃぁ各repositoryにgetInstance()を書くかっていわれたら、抽象クラスをつくりたくなりますよね。
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));
}
}
テストしてみます。
require_once('repository.php');
class TestRepository extends Repository
{
}
$test = TestRepository::getInstance();
var_dump($test); // object(TestRepository)[1]
できました。
(追記)php5.4以上ではtrait
を使ったほうがスマートじゃないか、という指摘を頂いたので、
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));
}
}
require_once('repository.php');
class TestRepository
{
use Repository;
}
$test = TestRepository::getInstance();
var_dump($test); // object(TestRepository)[1]
できました。