Казалось бы, из всех шаблонов проектирования что может быть проще всем известного синглетона. Во многих классических примерах реализации на разных языках программирования она может занимать всего пару десятков строк а того и меньше.
Так получилось, что я реализую этот шаблон вот уже второй год начиная с первого выхода PHP 5.3 в 2009 году. В то время у его предшественника версии 5.2 не было позднего статического связывания и для создания экземпляра класса в метод приходилось передавать его имя, что казалось мне архинеудобным.
С выходом PHP 5.4, взглянув еще раз на старую реализацию и на новые возможности языка, я переписал этот шаблон еще раз получив — как мне казалось тогда и кажется сейчас — конечный вариант.
Подробности реализации ниже.
Сразу бы хотелось отметить основные особенности:
Так получилось, что я реализую этот шаблон вот уже второй год начиная с первого выхода PHP 5.3 в 2009 году. В то время у его предшественника версии 5.2 не было позднего статического связывания и для создания экземпляра класса в метод приходилось передавать его имя, что казалось мне архинеудобным.
С выходом PHP 5.4, взглянув еще раз на старую реализацию и на новые возможности языка, я переписал этот шаблон еще раз получив — как мне казалось тогда и кажется сейчас — конечный вариант.
Подробности реализации ниже.
Сразу бы хотелось отметить основные особенности:
- Параметрическое порождение. Позволяет создавать экземпляры классов используя сигнатуру вызова метода ::getInstance. Каждой сигнатуре будет соответствовать свой экземпляр класса. По умолчанию такой тип порождения отключен. Включается в дочерних классах переопределением метода ::useParametricInstantiation.
- Получение дочернего объекта по имени родительского класса. Позволяет ссылаться на дочерние классы из родительских а также из других классов не зная их имени.
- Создание дочернего класса по имени родительского класса. Аналогично второму пункту, только в случае если дочерний объект не был
* Trait TSingleton.
* An implementation of the Singleton design pattern.
*/
namespace Traits;
/**
* var array variable holding all created objects.
*/
$objectPool = [];
trait TSingleton
{
/**
* Do not allow creating object by the new operator.
*
* final
* access private
* return void
*/
function __construct() { }
/**
* Do not allow cloning object.
*
* final
* access private
* return void
*/
private function __clone() { }
/**
* Called when class is being instantiated.
*
* access protected
* return void
*/
protected function onCreate() { }
/**
* User-level initialization routine.
*
* return void
*/
protected function init() { }
/**
* Returns true if child class has a parent specified by the mask.
*
* param string $child
* param string $parentMask
* final
* static
* access public
* return boolean
*/
static function hasParentClass($child, $parentMask)
{
$currentClass = get_parent_class($child);
if (!$currentClass)
return false;
do
{
if (strpos($currentClass, $parentMask) !== false)
return true;
}
while ($currentClass = get_parent_class($currentClass));
return false;
}
/**
* Returns instance of child class using its parent' class name specified
* by the mask. Always returns an array.
*
* param string $parentMask Any substring of parent's fully qualified class name.
* final
* static
* access public
* return array|null
*/
static function getObjectByParent($parentMask)
{
global $objectPool;
foreach ($objectPool as $class => $container)
if (self::hasParentClass($class, $parentMask))
return array_values($container);
return null;
}
/**
* Finds object(s) by the mask of its(their) parent's class namе. If not
* found the method will create it. Always returns an array.
*
* param string $parentMask
* param array $initArgs
* final
* static
* access public
* return array|null
*/
static function getObjectByParentSafe($parentMask, $initArgs = [])
{
$child = self::getObjectByParent($parentMask);
if ($child !== null)
return $child;
// Look up all declared classes.
$result = [];
foreach (get_declared_classes() as $class)
{
if (self::hasParentClass($class, $parentMask))
{
$result[] = call_user_func_array(($class. '::getInstance'), $initArgs);
}
}
return count($result)? $result: null;
}
/**
* Returns child object(s) of the parent class that called the method.
*
* see TSingleton::getObjectByParent
* final
* static
* access public
* return array|null
*/
static function getMyChild()
{
return self::getObjectByParent(get_called_class());
}
/**
* Safe variant of ::getMyChild.
*
* see TSingleton::getObjectByParentSafe
* final
* static
* access public
* return array
*/
static function getMyChildSafe()
{
$initArgs = func_get_args();
return self::getObjectByParent(get_called_class(), $initArgs);
}
/**
* Returns class instance.
*
* static
* final
* access public
* return TSingleton
*/
static function getInstance()
{
global $objectPool;
$argsArray = func_get_args();
$class = get_called_class();
if (static::useParametricInstantiation() && count($argsArray))
{
$fingerprint = '';
foreach ($argsArray as $arg)
$fingerprint .= serialize($arg);
$key = md5($class. $fingerprint);
}
else // Use class name as a key.
$key = $class;
if (!isset($objectPool[$class]))
$objectPool[$class] = []; // Init class objects container.
if (isset($objectPool[$class][$key]))
return $objectPool[$class][$key];
$instance = new $class();
// Add instance to the objects pool.
$objectPool[$class][$key] = $instance;
call_user_func_array([$instance, 'onCreate'], $argsArray);
$instance->init();
return $instance;
}
/**
* Enables or disables the parametric class instantiation. Disabled by default.
*
* access public
* static
* return boolean
*/
static function useParametricInstantiation()
{
return false;
}
}
:
Все созданные объекты теперь хранятся в одной переменной. Область видимости этой переменной (по просьбе трудящихся масс) ограничена примесью.
Дабы не рвать шаблоны нестандартным шаблоном синглетон с параметрическим порождением отныне называется мультитоном (Multiton).