一、什么是单例模式
单例即一个类只能有一个实例,并提供一个当前类的全局唯一访问入口(getInstance)。防止类被多次实例化和 克隆(clone)。
二、单例模式的特点
1、单例模式的要点:
- 构造函数需要标记为
private
(访问控制:防止外部代码使用new操作符创建对象),单例类不能在其他类中实例化,只能被其自身实例化; - 拥有一个保存类的实例的静态成员变量
- 拥有一个访问这个实例的公共的静态方法(常用
getInstance()
方法进行实例化单例类,通过instanceof操作符可以检测到类是否已经被实例化)
简单的记为三私一公一关键:
- 私有静态属性(privite static $instance),又来储存生成的唯一对象
- 私有构造函数 (privite __contruct())
- 私有克隆函数(privite function __clone()),防止克隆——clone
- 公共静态方法(public static function getInstance()),用来访问静态属性储存的对象,如果没有对象,则生成此单例
- 关键词
instanceof
,检查此变量是否为该类的对象、子类、或是实现接口。
三、单例模式的优点、缺点
优点
- 由于单例模式在内存中只有一个实例,减少内存开支,特别是一个对象需要频繁地创建销毁时,而且创建或销毁时性能又无法优化,单例模式就非常明显了。
- 由于单例模式只生成一个实例,所以,减少系统的性能开销,当一个对象产生需要比较多的资源时,如读取配置,产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后永久驻留内存的方式来解决。
- 单例模式可以避免对资源的多重占用,例如一个写文件操作,由于只有一个实例存在内存中,避免对同一个资源文件的同时写操作。
- 单例模式可以在系统设置全局的访问点,优化和共享资源访问,例如,可以设计一个单例类,负责所有数据表的映射处理。
缺点
- 单例模式一般没有接口,扩展很困难,若要扩展,除了修改代码基本上没有第二种途径可以实现。
- 单例对象如果持有Context,那么很容易引发内存泄漏,此时需要注意传递给单例对象的Context最好是Application Context。
四、应用场景(即什么时候要用它)
- 数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库资源。数据库软件系统中使用数据库连接池,主要是节省打开或者关闭数据库连接所引起的效率损耗,这种效率上的损耗还是非常昂贵的,因为何用单例模式来维护,就可以大大降低这种损耗。
- 多线程的线程池的设计一般也是采用单例模式,这是由于线程池要方便对池中的线程进行控制。
- 操作系统的文件系统,也是大的单例模式实现的具体例子,一个操作系统只能有一个文件系统。
HttpApplication
也是单位例的典型应用。熟悉http://ASP.Net(IIS)
的整个请求生命周期的人应该知道HttpApplication
也是单例模式,所有的HttpModule
都共享一个HttpApplication
实例.
总结以上,不难看出:
单例模式应用的场景一般发现在以下条件下:
- 资源共享的情况下,避免由于资源操作时导致的性能或损耗等。如上述中的日志文件,应用配置。
- 控制资源的情况下,方便资源之间的互相通信。如线程池等。
五、实例代码
基本讲解
单例模式解决的是如何在整个项目中创建唯一对象实例的问题,单例模式和工厂模式可以产生更加合理的对象。
<?php
/**
* @purpose: 创建一个单例类
* Class Single
*/
class Single {
/**
* @var Object 保存类实例的静态成员变量
*/
private static $_instance;
/**
* Single constructor. 私有的构造方法
*/
private function __construct(){
echo 'This is a Constructed method;';
}
/**
* @purpose: 创建__clone方法防止对象被复制克隆
*/
privite function __clone(){
// E_USER_ERROR只能通过trigger_error($msg, E_USER_ERROR)手动触发。
// E_USER_ERROR是用户自定义错误类型,可以被set_error_handler错误处理函数捕获,允许程序继续运行。
// E_ERROR是系统错误,不能被set_error_handler错误处理函数捕获,程序会退出运行
trigger_error('Clone is not allow!',E_USER_ERROR);
}
/**
* @return Single|Object 单例方法,用于访问实例的公共的静态方法
*/
public static function getInstance(){
if(!(self::$_instance instanceof self)){
self::$_instance = new self;
}
return self::$_instance;
}
/**
* @purpose: 测试方法
*/
public function test(){
echo '调用方法成功';
}
}
单例模式,使某个类的对象仅允许创建一个。构造函数private修饰,
申明一个static getInstance方法,在该方法里创建该对象的实例。如果该实例已经存在,则不创建。比如只需要创建一个数据库连接。
<?php
//final防止类被继承
final class DBHelper{
//初始化实例 含义:$install = new DBHelper()...
private static $instance=null;
//构造器私有,防止类外部实例化
private function __construct(){
//连接数据库操作
}
//获取实例
public static function getInstance(){
if(!(self::$instance instanceof self)){
self::$instance=new self;
}
return self::$instance;
}
//防止实例被克隆
private function __clone(){
}
}
$conn1=DBHelper::getInstance();
$conn2=DBHelper::getInstance();
echo $conn1 === $conn2;
例:php创建MySQL单例模式
<?php
// 单例模式创建最终的数据库类使用集
final class Db{
//私有的静态的用于对象的属性
private static $obj = NULL;
// 数据库基本属性
private $db_host; //主机名
private $db_user; //用户名
private $db_pass; //密码;
private $db_name; //数据库名
private $charset; //字符编码
// 私有的构造方法,阻止类外new对象,,,,
// 对象的初始化(给属性赋值)
private function __construct(){
$this->db_host = $GLOBALS['config']['db_host'];
$this->db_user = $GLOBALS['config']['db_user'];
$this->db_pass = $GLOBALS['config']['db_pass'];
$this->db_name = $GLOBALS['config']['db_name'];
$this->charset = $GLOBALS['config']['charset'];
$this->connectDb();
$this->selectDb();
$this->setCharset();
}
//私有的克隆方法,组织类外克隆对象
private function __clone(){}
// 共有的静态的,创建对象的方法
public static function getInstance(){
// 判断该对象是否是该类的对象并创建
if (!self::$obj instanceof self) {
self::$obj = new self();
}
// 否则返回
return self::$obj;
}
//私有的连接数据库服务器的方法
private function connectDb(){
if (!@mysql_connect($this->db_host,$this->db_user,$this->db_pass))
die('PHP连接MYSQL服务器失败');
}
// 私有的连接数据库的方法
private function selectDb(){
if (!mysql_select_db($this->db_name)) die('选择数据库失败');
}
//设置字符集的方法
private function setCharset(){
$this->exec("set names $this->charset");
}
// 数据库对数据增删改的方法,
public function exec($sql){
$sql = strtolower($sql);
if (!substr($sql,0,6)=='select') {
die('该方法不能执行select 语句');
}
return mysql_query($sql);
}
//执行select语句的方法
private function query($sql){
$sql = strtolower($sql);
if (!substr($sql,0,6)=='select') {
die('此方法只能执行select语句');
}
return mysql_query($sql);
}
// 返回单行结果集的方法
public function fetchOne($sql,$type=3){
// 执行sql语句
$result = $this->query($sql);
$types = array(
1 => MYSQL_NUM,
2 => MYSQL_BOTH,
3 => MYSQL_ASSOC
);
return mysql_fetch_array($result,$types[3]);
}
// 返回多行数据
public function fetchArray($sql,$type=3){
//执行sql语句
$res = $this->query($sql);
$types = array(
1 => MYSQL_NUM,
2 => MYSQL_BOTH,
3 => MYSQL_ASSOC
);
$arr = array();
while($row = mysql_fetch_array($res,$types[3])){
$arr[] = $row;
}
return $arr;
}
//获取记录行的方法
public function getCount($sql){
$res = $this->query();
return mysql_num_rows($res);
}
}