数字营销 · Web开发 · 基础设施

PHP和JavaScript单例模式的对比

通过 PHP 和 JavaScript 两个例子,对比单例模式的实现方式,以及如何避免对象被重复实例化。

什么是单例模式?先来看一下维基百科的解释:

单例模式,也叫单子模式,是一种常用的软件设计模式。在应用这个模式时,单例对象的类必须保证只有一个实例存在。许多时候整个系统只需要拥有一个全局对象,这样有利于我们协调系统整体的行为。

比如在某个服务器程序中,服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息。这种方式可以简化复杂环境下的配置管理。

这个概念理解起来可能比较困难,用最简单的话来概括:

单例模式就是在创建一个类的时候,通过某种方法保证这个类只能产生一个实例。

这么做有什么好处呢?

我们知道,当编写好一个类之后,要产生这个类的实例是非常简单的,直接使用 new 关键字就可以。

但是在一些大型项目当中,同一个页面可能并不是一个人开发。

例如有一个 MySQL 类,第一个开发人员实例化了一个 MySQL 对象,第二个人开发的时候,可能又实例化了一个 db 对象。

如果这两个对象实际上承担的是同样的功能,那么每次实例化一个对象的时候,都可能需要重新连接一次数据库,这样就会造成资源浪费。

单例模式的出现,就是为了避免同一个类被重复实例化,从而节省系统资源。

PHP 中怎么实现单例模式?

首先来看一下 PHP 中怎么实现单例模式。

我们知道,当使用 new 实例化一个对象的时候,类中的 __construct 方法会自动执行。

那么,我能不能把这个方法设置为私有或者受保护,从而阻止外部直接使用 new 来创建对象呢?

来看下面这个例子:

<?php

class Human {

static protected $obj = NULL; // 定义类的静态属性

final protected function __construct(){
// 防止外部直接 new Human
}

static public function getInstance(){
// 定义静态方法,用来获取对象

if(self::$obj instanceof self){
return self::$obj;
}else{
self::$obj = new self;
return self::$obj;
}
}

final protected function __clone(){
// 防止对象被克隆
}

public function say(){
echo "Hello";
}
}

class Stu extends Human {
public $name = "Hito";
}

$a1 = Human::getInstance();
$a2 = Human::getInstance();
$a3 = Stu::getInstance();

if($a1 === $a2 && $a2 === $a3){
echo "对象相同";
}else{
echo "对象不相同";
}

$b1 = new Human();
// 错误:Call to protected Human::__construct() from invalid context

$b2 = new Stu();
// 错误:Call to protected Human::__construct() from invalid context

$a4 = clone $a3;
// Fatal error:无法访问受保护的 __clone()

$a3->name;
// Undefined property: Human::$name

?>

PHP 单例模式的原理

原理其实比较简单。

首先,定义一个 Human 的受保护静态属性:

static protected $obj = NULL;

这个属性用来保存最终产生的对象,初始化为空。

第二,定义类的 __construct() 方法为 final 和 protected。

其中,protected 可以防止外部直接调用这个构造函数,也就不能直接通过 new Human() 来创建对象。

final 则可以保证继承自 Human 的子类不能重新定义这个构造方法。

第三,定义一个公共静态方法 getInstance() 用来获取对象。

为了保证多次获取到的对象都是同一个对象,最终产生的对象会被保存到 self::$obj 中。

当再次调用 getInstance() 时,先判断 self::$obj instanceof self , 如果已经存在对象,就直接返回 self::$obj

如果还没有产生对象,就先 self::$obj = new self 创建对象,再返回这个对象。

这样就可以保证多次调用 getInstance() 得到的都是同一个对象。

第四,再定义一个不能被子类改写的 __clone() 方法,防止已经产生的对象通过 clone 得到一个新的对象。

最后,就是正常提供给外部调用的公共方法。

关于 Stu 类

可以看到,Stu 这个类是继承于 Human 的:

class Stu extends Human {
public $name = "Hito";
}

但是 $a3 = Stu::getInstance() 最终得到的仍然是 Human 的实例。也就是说,在 Stu 中定义的公共属性和方法,$a3 并不能直接访问。

例如:

$a3->name;

会提示:Undefined property: Human::$name

这也是上面代码中最后一个例子所体现的问题。

PHP 单例模式的几个特点

总结一下 PHP 单例模式:

  1. 通过定义类的构造函数为私有或受保护,防止使用 new 直接实例化;
  2. 通过 final 保证相关方法不能被子类重新定义;
  3. 通过定义 __clone(),防止对象被克隆;
  4. 设置一个公共静态方法来产生实例,并把实例保存到类的静态属性中,以后每次获取对象时,都返回这个属性所指向的对象。

那么,在 JavaScript 中,单例模式又是什么样的呢?

JavaScript 中的单例模式

从上面的概念可以看出,单例模式主要是为了避免同一个对象被重复实例化。

在 JavaScript 中,除了我们自己定义的构造函数之外,对象字面量本身不能通过 new 再创建一个新的实例,因为它们本身并不是构造函数。

最简单的单例模式可以直接写成:

var Human= {
Name: "Hito",
Age: 25
};

或者通过函数返回一个对象:

var Human= (function(){
var Name="Hito";
var Age=25;

return {
Name: Name,
Age: Age
};

})();

这里的 Human 本身已经是一个对象,所以不能这样使用:

var xiaoming=newHuman();

因为 Human 不是一个构造函数。

JavaScript 中另一种单例实现方式

不过,上面的两种方法虽然可以实现单例模式,但是并没有像 PHP 一样直接限制类的 new 操作。

还可以通过下面这种方式实现:

function Human(){

if(Human.instance){
return Human.instance;
}

this.name ="Hito";
this.Age =25;

Human.instance = this;
}

var a1=newHuman();
var a2=newHuman();

alert(a1===a2); // true

在这个例子中,定义了一个 Human 类,同时给 Human 增加一个 instance 属性。

第一次实例化的时候:

var a1=newHuman();

由于 Human.instance

还不存在,所以正常创建对象,并把当前对象保存下来:

Human.instance =this;

第二次再执行:

var a2=newHuman();

这时候已经存在 Human.instance, 于是直接返回之前保存的对象。

所以 a1===a2 结果就是 true

这里的主要思路,就是通过保存已经创建的对象,在再次实例化的时候直接返回之前保存的对象,从而保证整个过程中只有一个实例。

评论0

欢迎分享你的看法,也欢迎补充不同的实践经验。