我正在使用一个 Web 应用程序框架,其中一部分由许多服务组成,所有服务均以单例形式实现。它们都扩展了一个 Service 类,其中实现了单例行为,看起来像这样:
class Service {
protected static $instance;
public function Service() {
if (isset(self::$instance)) {
throw new Exception('Please use Service::getInstance.');
}
}
public static function &getInstance() {
if (empty(self::$instance)) {
self::$instance = new self();
}
return self::$instance;
}
}
现在,如果我有一个名为 FileService 的类,如下实现:
class FileService extends Service {
// Lots of neat stuff in here
}
...调用 FileService::getInstance() 不会像我希望的那样生成 FileService 实例,而是生成 Service 实例。我认为这里的问题是服务构造函数中使用的“self”关键字。
还有其他方法可以实现我想要的吗?单例代码只有几行,但我仍然希望尽可能避免任何代码冗余。
代码:
abstract class Singleton
{
protected function __construct()
{
}
final public static function getInstance()
{
static $instances = array();
$calledClass = get_called_class();
if (!isset($instances[$calledClass]))
{
$instances[$calledClass] = new $calledClass();
}
return $instances[$calledClass];
}
final private function __clone()
{
}
}
class FileService extends Singleton
{
// Lots of neat stuff in here
}
$fs = FileService::getInstance();
如果您使用 PHP < 5.3, add this too:
// get_called_class() is only in PHP >= 5.3.
if (!function_exists('get_called_class'))
{
function get_called_class()
{
$bt = debug_backtrace();
$l = 0;
do
{
$l++;
$lines = file($bt[$l]['file']);
$callerLine = $lines[$bt[$l]['line']-1];
preg_match('/([a-zA-Z0-9\_]+)::'.$bt[$l]['function'].'/', $callerLine, $matches);
} while ($matches[1] === 'parent' && $matches[1]);
return $matches[1];
}
}
如果我在 5.3 课上多加注意,我自己就会知道如何解决这个问题。使用 PHP 5.3 新的后期静态绑定功能,我相信 Coronatus 的主张可以简化为:
class Singleton {
protected static $instance;
protected function __construct() { }
final public static function getInstance() {
if (!isset(static::$instance)) {
static::$instance = new static();
}
return static::$instance;
}
final private function __clone() { }
}
我尝试了一下,效果非常好。不过,5.3 之前的版本仍然是一个完全不同的故事。
我找到了一个很好的解决方案。
以下是我的代码
abstract class Singleton
{
protected static $instance; // must be protected static property ,since we must use static::$instance, private property will be error
private function __construct(){} //must be private !!! [very important],otherwise we can create new father instance in it's Child class
final protected function __clone(){} #restrict clone
public static function getInstance()
{
#must use static::$instance ,can not use self::$instance,self::$instance will always be Father's static property
if (! static::$instance instanceof static) {
static::$instance = new static();
}
return static::$instance;
}
}
class A extends Singleton
{
protected static $instance; #must redefined property
}
class B extends A
{
protected static $instance;
}
$a = A::getInstance();
$b = B::getInstance();
$c = B::getInstance();
$d = A::getInstance();
$e = A::getInstance();
echo "-------";
var_dump($a,$b,$c,$d,$e);
#object(A)#1 (0) { }
#object(B)#2 (0) { }
#object(B)#2 (0) { }
#object(A)#1 (0) { }
#object(A)#1 (0) { }
您可以参考http://php.net/manual/en/language.oop5.late-static-bindings.php 了解更多信息
这是固定的约翰的答案。 PHP 5.3+
abstract class Singleton
{
protected function __construct() {}
final protected function __clone() {}
final public static function getInstance()
{
static $instance = null;
if (null === $instance)
{
$instance = new static();
}
return $instance;
}
}
我遇到这个问题是因为我正在使用 Singleton 类来管理类似缓存的对象并想要扩展它。 Amy B 的答案看起来有点太复杂了,不适合我的口味,所以我进一步挖掘,这就是我想到的,就像魅力一样:
abstract class Singleton
{
protected static $instance = null;
protected function __construct()
{
}
final public static function getInstance()
{
if (static::$instance === null) {
static::$instance = new static();
}
return static::$instance;
}
final private function __clone()
{
}
}
class FileService extends Singleton
{
protected static $instance = null;
}
$fs = FileService::getInstance();
只需覆盖 $instance 类属性即可解决该问题。 仅使用 PHP 8 进行了测试,但我猜测这也适用于旧版本。
使用特征而不是抽象类允许扩展单例类。
使用 SingletonBase 特性作为父单例类。
使用 SingletonChild 特性作为其单例子项。
interface Singleton
{
public static function getInstance(): Singleton;
}
trait SingletonBase
{
private static $instance=null;
abstract protected function __construct();
public static function getInstance(): Singleton {
if (is_null(self::$instance)) {
self::$instance=new static();
}
return self::$instance;
}
protected function clearInstance(): void {
self::$instance=null;
}
public function __clone()/*: void*/ {
trigger_error('Class singleton '.get_class($this).' cant be cloned.');
}
public function __wakeup(): void {
trigger_error('Classe singleton '.get_class($this).' cant be serialized.');
}
}
trait SingletonChild
{
use SingletonBase;
}
class Bar
{
protected function __construct(){
}
}
class Foo extends Bar implements Singleton
{
use SingletonBase;
}
class FooChild extends Foo implements Singleton
{
use SingletonChild; // necessary! If not, the unique instance of FooChild will be the same as the unique instance of its parent Foo
}
private static $_instances = [];
/**
* gets the instance via lazy initialization (created on first usage).
*/
public static function getInstance():self
{
$calledClass = class_basename(static::class);
if (isset(self::$_instances[$calledClass])) {
self::$_instances[$calledClass] = new static();
}
return self::$_instances[$calledClass];
}
这个唯一的问题是,如果你有同名的单例。
改进的 Amy B 解决方案,没有 get_used_class() 函数
class Singleton
{
protected static $instances = [];
final private function __clone()
{
}
public static function getInstance()
{
if (!isset(static::$instances[static::class])) {
static::$instances[static::class] = new static();
}
return static::$instances[static::class];
}
}
class A extends Singleton
{
}
class B extends Singleton
{
}
$a = A::getInstance();
$a->var = "I'm A";
$b = B::getInstance();
$c = A::getInstance();
var_dump($a, $b, $c);
它将显示:
object(A)#1 (1) {
["var"]=>
string(5) "I'm A"
}
object(B)#2 (0) {
}
object(A)#1 (1) {
["var"]=>
string(5) "I'm A"
}