问题 在PHP 5.3中创建Singleton基类


开门见山: 我有两个单身人士课程,都是从超级课程继承他们的单身人士。我在第一个单例上初始化一些属性,然后检索第二个单例  第一个的例子。然而,那个例子似乎并不是我最初初始化的那个例子。一些示例代码可能有助于解释这一点:

首先,超类,提供单例性质(需要PHP 5.3或更高版本):

class Singleton {

    protected static $instance;

    protected function __construct() { }

    final private function __clone() { }

    public static function getInstance() {
        if (!(static::$instance instanceof static)) {
            static::$instance = new static();
        }
        return static::$instance;
    }

}

然后我们得到了第一个带有值的单身人士:

require_once('Singleton.php');

class SingletonA extends Singleton {

    protected $value;

    public function SingletonA() {
        $this->value = false;
    }

    public function getValue() {
        return $this->value;
    }

    public function setValue($value) {
        $this->value = $value;
    }

}

然后引用第一个单例的第二个单例:

require_once('Singleton.php');
require_once('SingletonA.php');

class SingletonB extends Singleton {

    public function getValue() {
        return SingletonA::getInstance()->getValue();
    }

}

现在进行测试,显示失败的方式:

require_once('SingletonA.php');
require_once('SingletonB.php');

SingletonA::getInstance()->setValue(true);

echo (SingletonA::getInstance()->getValue()) ? "true\n" : "false\n";
echo (SingletonB::getInstance()->getValue()) ? "true\n" : "false\n";

测试产生以下输出:

true
false

显然,测试代码引用的SingletonA实例与SingletonB实例引用的实例不同。简而言之,SingletonA并不像我需要的那样单一。这怎么可能?我能用什么魔法来弥补这种行为,给我一个真正的单身人士?


1245
2017-10-19 20:48


起源

这是我第一次遇到继承的Singleton行为。无论它在语法上是否正确,都很奇怪。 - spender
SingletonA永远不会是SingletonB的一个实例,即使它们都是单身人士;该 static 关键字将确保这一点。 - thetaiko
为什么不使用Singleton? - Stephen
是的,我认为我没有设法清楚地传达这一点。我不认为SingletonA和SingletonB是同一个实例。恰恰相反,它们是两种不同类型的两个不同实例。它们共享一个共同的祖先,使它们都是Singleton类型,但这不是测试代码检索的类型。我希望测试代码在SingletonA中设置字段值,然后我想要SingletonB(完全是一个不同的实例和类型)来检索SingletonA实例并能够检索其字段的值。 - Johan Fredrik Varen
你确定你真的需要10个不同的单身人士吗?这就是那种难以调试/测试问题的东西,就像你在这里遇到的问题一样。如果您将A的实例作为参数传递给B的构造函数(并且A和B不是单例),那么您首先不会遇到此问题。 - rojoca


答案:


尝试使用 isset 而不是 instanceof

class Singleton {
    protected static $instances;

    protected function __construct() { }

    final private function __clone() { }

    public static function getInstance() {
        $class = get_called_class();

        if (!isset(self::$instances[$class])) {
            self::$instances[$class] = new $class;
        }
        return self::$instances[$class];
    }
}

11
2017-10-19 21:01



感谢lonesomeday,但这给了我相同的实例,这不是我想要的。 - Johan Fredrik Varen
@Johan你是对的,确实如此。对于那个很抱歉。我已经使用一系列实例重写了你的类 Singleton::$instances,这确实有效。 - lonesomeday
万分感谢,伙计!据我所知,这个版本甚至可以在PHP版本<5.3上运行。但我仍然不明白为什么它不起作用。 - Johan Fredrik Varen
它不会在5.3之前工作,因为 get_called_class 与...同时推出 static 在5.3。我也不确定问题究竟是什么! - lonesomeday


SingletonA 和 SingletonB 是不同的类。虽然它们是从同一个类继承的,但它们是独立的类,因此它们具有不同的静态实例。

如果您更改代码以获得2个实例 SingletonA 或2个实例 SingletonB,你会看到你期望的行为。但因为他们是不同的阶级,他们不是同一个单身人士。


0
2017-10-19 20:59





我很确定这是因为你使用的是非实例化的静态方法。


0
2017-10-19 20:55





我们来谈谈OO。 :)

SingletonA 和 SingletonB 是类型的 Singleton

因此可以说:

SingletonA    Singleton

 

SingletonB    Singleton 

他们都是 Singleton 

预期的意义 Singleton 意味着只能有一个。使用您的代码的OO背景中的许多人将会感到困惑。

通常,Singleton的实现将基于每个类,因为大多数OO语言都不会倾向于允许您提出的意图。

PHP可能会做(通过 get_called_class() 魔术)并不意味着它应该。

我绝对可以接受,从功利主义的角度来看,接受的答案看起来很好。考虑到接受的答案的优点,我建议更改名称,与“标准”Singleton实现不冲突。从严格的OO角度来看,人们永远不会从Singleton继承,因此它确实需要一个不同的名称。


0
2017-10-19 22:01



感谢您花时间以如此慷慨的方式回应。在谈到单例模式背后的理论时,我不会说太多专业知识,但我同意SingletonA和SingletonB都可以说是Singleton的实例,它们也是不同类型的实例。我无法找到解决模式继承方面的任何定义,但我看到它的方式是,如果一个丛林只能容纳一只老虎和一只狮子,它们可以共存,即使它们是两个猫科动物。再说一次,我声称对此的了解不如我自己的狂言。 - Johan Fredrik Varen
我知道你在这里的意思,我不会设计多个单身的东西(我最多使用一个,并将所有其他实例存储在那个中)。我同意将(a)重命名为基类会更好 BaseSingleton 或类似的东西。 - lonesomeday