Так, я всегда реализовывал одиночный элемент как так:
class Singleton {
private static $_instance = null;
public static function getInstance() {
if (self::$_instance === null) self::$_instance = new Singleton();
return self::$_instance;
}
private function __construct() { }
}
Однако это недавно ударило меня, что я мог также реализовать его с мудрыми участником статическими переменными:
class Singleton {
public static function getInstance() {
//oops - can't assign expression here!
static $instance = null; // = new Singleton();
if ($instance === null) $instance = new Singleton();
return $instance;
}
private function __construct() { }
}
Мне это более чисто, потому что это не создает помехи классу, и я не должен делать никакой явной проверки существования, но потому что я никогда не видел эту реализацию больше нигде, я задаюсь вопросом:
Есть ли что-то не так с использованием второй реализации по первому?
Вероятно, вы имеете в виду это с небольшим изменением (иначе я получил синтаксическую ошибку):
<?php
class Singleton {
public static function getInstance() {
static $instance;
if ($instance === null)
$instance = new Singleton();
xdebug_debug_zval('instance');
return $instance;
}
private function __construct() { }
}
$a = Singleton::getInstance();
xdebug_debug_zval('a');
$b = Singleton::getInstance();
xdebug_debug_zval('b');
Это дает:
instance: (refcount=2, is_ref=1), object(Singleton)[1]
a: (refcount=1, is_ref=0), object(Singleton)[1]
экземпляр: (refcount=2, is_ref=1), object(Singleton)[1]
b: (refcount=1, is_ref=0), object(Singleton)[1]
Таким образом, у него есть недостаток - при каждом вызове будет создаваться новый zval. Это не особенно серьезно, так что если вам так больше нравится, продолжайте.
Причина принудительного разделения zval в том, что внутри getInstance
, $instance
является ссылкой (в смысле =&
, и имеет счетчик ссылок 2 (одна для символа внутри метода, другая для статического хранилища). Поскольку getInstance
не возвращается по ссылке, zval должен быть разделен -- для возврата создается новый с количеством ссылок 1 и очищенным флагом ссылки.
После некоторой игры лучший метод, который я могу придумать, выглядит следующим образом:
Создайте файл с именем SingletonBase.php и включите его в корень вашего скрипта!
Код
abstract class SingletonBase
{
private static $storage = array();
public static function Singleton($class)
{
if(in_array($class,self::$storage))
{
return self::$storage[$class];
}
return self::$storage[$class] = new $class();
}
public static function storage()
{
return self::$storage;
}
}
Тогда для любого класса, который вы хотите создать синглтон, просто добавьте этот небольшой одиночный метод.
public static function Singleton()
{
return SingletonBase::Singleton(get_class());
}
Вот небольшой пример:
include 'libraries/SingletonBase.resource.php';
class Database
{
//Add that singleton function.
public static function Singleton()
{
return SingletonBase::Singleton(get_class());
}
public function run()
{
echo 'running...';
}
}
$Database = Database::Singleton();
$Database->run();
И вы можете просто добавить эту одноэлементную функцию в любой имеющийся у вас класс, и она создаст только 1 экземпляр для каждого класса.
Еще одна идея, которую вы также можете сделать
if(class_exists('Database'))
{
$Database = SingletonBase::Singlton('Database');
}
, и в конце вашего скрипта вы также можете выполнить некоторую dfebugging, если вам тоже нужно,
в конце вашего скрипта вы можете просто выполнить
foreach(SingletonBase::storage () as $name => $object)
{
if(method_exists("debugInfo",$object))
{
debug_object($name,$object,$object->debugInfo());
}
}
, чтобы этот метод отлично подходит для отладчика, чтобы получить доступ ко всем классам и состояниям объектов, которые были инициализированы
Выбирайте классную собственность. Есть несколько преимуществ ...
class Foo {
protected static $instance = null;
public static function instance() {
if (is_null(self::$instance)) {
self::$instance = new Foo();
}
return self::$instance;
}
}
Во-первых, проще выполнять автоматические тесты. Вы можете создать фиктивный класс foo для «замены» экземпляра, чтобы другие классы, зависящие от foo, получали копию макета вместо оригинала:
class MockFoo extends Foo {
public static function initialize() {
self::$instance = new MockFoo();
}
public static function deinitialize() {
self::$instance = null;
}
}
Затем в ваших тестовых примерах (при условии, что phpunit):
protected function setUp() {
MockFoo::initialize();
}
protected function tearDown() {
MockFoo::deinitialize();
}
Это позволяет обойти обычную проблему с синглтонами, которую сложно протестировать.
Во-вторых, это делает ваш код более гибким. Если вы когда-нибудь захотите «заменить» функциональность во время выполнения в этом классе, все, что вам нужно сделать, это создать подкласс и заменить self :: $ instance
.
В-третьих, это позволяет вам работать с экземпляром в другой статической функции. Это не имеет большого значения для классов с одним экземпляром (настоящий синглтон), поскольку вы можете просто вызвать self :: instance ()
. Но если у вас есть несколько «именованных» копий (скажем, для подключений к базе данных или других ресурсов, где вы хотите иметь более одной, но не хотите создавать новую, если они уже существуют), она становится грязной, потому что вам нужно отслеживать имен:
protected static $instances = array();
public static function instance($name) {
if (!isset(self::$instances[$name])) {
self::$instances[$name] = new Foo($name);
}
return self::$instances[$name];
}
public static function operateOnInstances() {
foreach (self::$instances as $name => $instance) {
//Do Something Here
}
}
Еще одно замечание: я бы не стал делать конструктор закрытым. Это сделает невозможным правильное расширение или тестирование. Вместо этого сделайте его защищенным, чтобы при необходимости можно было подклассифицировать и по-прежнему работать с родительским элементом ...
Самым чистым решением является удаление логики синглтона из самого класса (потому что это что-то не связано с работой самого класса).
Интересную реализацию см. Здесь: http://phpgoodness.wordpress.com/2010/07/21/singleton-and-multiton-with-a-different-approach/