什么是单例模式?先来看一下维基百科的解释:
单例模式,也叫单子模式,是一种常用的软件设计模式。在应用这个模式时,单例对象的类必须保证只有一个实例存在。许多时候整个系统只需要拥有一个全局对象,这样有利于我们协调系统整体的行为。
比如在某个服务器程序中,服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息。这种方式可以简化复杂环境下的配置管理。
这个概念理解起来可能比较困难,用最简单的话来概括:
单例模式就是在创建一个类的时候,通过某种方法保证这个类只能产生一个实例。
这么做有什么好处呢?
我们知道,当编写好一个类之后,要产生这个类的实例是非常简单的,直接使用 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 单例模式:
- 通过定义类的构造函数为私有或受保护,防止使用 new 直接实例化;
- 通过 final 保证相关方法不能被子类重新定义;
- 通过定义 __clone(),防止对象被克隆;
- 设置一个公共静态方法来产生实例,并把实例保存到类的静态属性中,以后每次获取对象时,都返回这个属性所指向的对象。
那么,在 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
欢迎分享你的看法,也欢迎补充不同的实践经验。