PHP 单例模式:分步和解决问题实现
从头开始设计 PHP 中的单例模式
引言
设计模式是软件开发中常见问题的经过良好测试的解决方案。在著名的“四人帮”书中,创建了 23 种设计模式,它们已广泛应用于面向对象编程中,例如 Java 和 C#。PHP 怎么样?自 PHP 5.0 以来,它已成为一种完整的面向对象语言,并且一些设计模式也已广泛应用于 PHP 及其众多的 MVC 框架中。今天,让我们使用逐步和问题解决的方法在 PHP 中实现最常见的单例模式。
场景
在 PHP 开发中(尤其是在使用 MVC 框架时),我们经常需要包含/引用一个 DB 类、一个 Upload 类或一个 Cookie 类,如下所示
<?php
require 'DB.class.php';
require 'Upload.class.php';
require 'Cookie.class.php';
但我们需要确保 DB 类或 Cookie 类只有一个实例,因为一个实例就足够了,更多实例会带来问题。 这就是单例模式发挥作用的地方。 简而言之,单例模式意味着如果程序员已经实例化了某个类的对象,则另一个程序员无法实例化第二个对象。 现在让我们看看如何从头开始做到这一点。
实现
第一步:创建一个公共类。
我们只需创建一个名为 Singleton 的类,现在我们可以实例化任意数量的对象。 这里我们可以使用 Singleton 类创建两个对象
class Singleton {
}
$s1 = new Singleton();
$s2 = new Singleton();
$s1 和 $s2 是同一个对象吗? 当然不是,我们可以使用一个简单的 if() 语句来判断
class Singleton {
}
$s1 = new Singleton();
$s2 = new Singleton();
if ($s1 === $s2) {
echo 's1 and s2 are the same object';
} else {
echo 's1 and s2 are NOT the same object';
}
注意:在判断两个对象是否相同时,必须使用“===”,而不是“==”。
第二步:禁止“new”操作
如果我们想阻止类用户创建他们想要的任意数量的 Singleton 类对象,该怎么办? 嗯,你可能会认为对象是由类的构造函数创建的。 那么,我们如何将构造函数隐藏在类本身中,以便外部无法使用它呢? 就像下面的代码一样
class Singleton2 {
protected function __construct() {
}
}
$s3 = new Singleton2();
//Fatal error: Call to protected Singleton2::__construct() from invalid context
现在你可以看到我们使构造函数受到保护,但现在的新问题是:没有人可以再创建对象了。
第三步:创建一个对象创建方法
为了让类用户可以创建一个对象,我们需要保留一个对象创建“接口”,因此我们添加了一个名为 getIns() 的新方法,它是public(对外部开放)和 static(因此可以使用类名调用它)
class Singleton3 {
public static function getIns() {
return new self();
}
protected function __construct() {
}
}
我们只是在类中创建了一个方法。 在 getIns() 中,我们创建类本身的对象,然后返回它。 现在我们再次测试
class Singleton3 {
public static function getIns() {
return new self();
}
protected function __construct() {
}
}
$s4 = Singleton3::getIns();
$s5 = Singleton3::getIns();
if ($s4 === $s5) {
echo 's4 and s5 are the same object';
} else {
echo 's4 and s5 are NOT the same object';
}
结果是:
s4 and s5 are NOT the same object
这两个对象仍然不是同一个对象,为什么? 因为 Singleton3::getIns() 被调用了两次,因此创建了两个对象。 但是现在,控制权在我们手中,我们可以改进 getIns() 以使其执行我们想要的操作。
第四步:改进 getIns() 方法
现在我们可以在 getIns() 中添加一些检查
class Singleton4 {
protected static $ins = NULL;
public static function getIns() {
if (self::$ins === null) {
self::$ins = new self();
}
return self::$ins;
}
protected function __construct() {
}
}
$s6 = Singleton4::getIns();
$s7 = Singleton4::getIns();
if ($s6 === $s7) {
echo 's6 and s7 are the same object';
} else {
echo 's6 and s7 are NOT the same object';
}
你可以看到,现在我们将类的实例存储在一个名为 $ins 的受保护属性中。 然后当 getIns() 被调用时,我们会进行一些检查:如果 $ins 为 NULL,那么我们实例化一个类对象。 最后,我们返回 self::$ins。
让我们测试一下
s6 and s7 are the same object
现在我们得到了我们想要的结果! 现在类只有一个实例。 但这仍然不够,为什么? 如果有另一个名为 Multi 的类并且它继承了我们的原始类
class Singleton4 {
protected static $ins = NULL;
public static function getIns() {
if (self::$ins === null) {
self::$ins = new self();
}
return self::$ins;
}
protected function __construct() {
}
}
class Multi extends Singleton4 {
public function __construct() {
}
}
$s6 = Singleton4::getIns();
$s7 = Singleton4::getIns();
if ($s6 === $s7) {
echo 's6 and s7 are the same object';
} else {
echo 's6 and s7 are NOT the same object';
}
echo '<br>';
$s8 = new Multi();
$s9 = new Multi();
if ($s8 === $s9) {
echo 's8 and s9 are the same object';
} else {
echo 's8 and s9 are NOT the same object';
}
在这个子类中,父构造函数的可见性更改为 public,现在会发生什么
s6 and s7 are the same object s8 and s9 are NOT the same object
子类中的这一行代码
public function __construct() {
}
摧毁了我们迄今为止所做的一切! 因此我们需要阻止我们的子类更改父类构造函数的可见性。
第五步:防止子类覆盖父类构造函数
现在我们需要在父构造函数的前面添加关键字 final
class Singleton5 {
protected static $ins = NULL;
public static function getIns() {
if (self::$ins === null) {
self::$ins = new self();
}
return self::$ins;
}
// Adding final, so this method cannot be override!
final protected function __construct() {
}
}
所以现在如果我们再次运行脚本
class Singleton5 {
protected static $ins = NULL;
public static function getIns() {
if (self::$ins === null) {
self::$ins = new self();
}
return self::$ins;
}
// Adding final, so this method cannot be override!
final protected function __construct() {
}
}
我们看到输出
Fatal error: Cannot override final method Singleton5::__construct()
我们完成了吗? 还没有……如果有人编写代码
class Singleton5 {
protected static $ins = NULL;
public static function getIns() {
if (self::$ins === null) {
self::$ins = new self();
}
return self::$ins;
}
// Adding final, so this method cannot be override!
final protected function __construct() {
}
}
$s10 = Singleton5::getIns();
$s11 = clone $s10; // s11 cloned s10, then more than one object is created!
if ($s10 === $s11) {
echo 's10 and s11 are the same object';
} else {
echo 's10 and s11 are NOT the same object';
}
结果
s10 and s11 are NOT the same object
这是因为这里使用了 clone 关键字。 所以问题再次发生。 如何防止克隆?
第六步:禁止 __clone() 魔术函数
class Singleton6 {
protected static $ins = NULL;
public static function getIns() {
if (self::$ins === null) {
self::$ins = new self();
}
return self::$ins;
}
// Adding final, so this method cannot be override!
final protected function __construct() {
}
// Forbid clone
final protected function __clone(){
}
}
$s10 = Singleton6::getIns();
$s11 = clone $s10; // Fatal error: Call to protected Singleton6::__clone()
现在不可能克隆了。
最后,我们完成了 PHP 中的单例模式!
我希望这种逐步实现单例的方法对你有所帮助。 了解设计模式是好事,但我想,如果我们知道为什么我们需要创建这样的模式,以及如何迭代地改进我们的代码以实现我们的目标,那就更重要和有用了。