详解PHP设计模式之单例模式

777 阅读2分钟

一、什么是单例模式

单例即一个类只能有一个实例,并提供一个当前类的全局唯一访问入口(getInstance)。防止类被多次实例化和 克隆(clone)。

二、单例模式的特点

1、单例模式的要点:

  • 构造函数需要标记为private(访问控制:防止外部代码使用new操作符创建对象),单例类不能在其他类中实例化,只能被其自身实例化;
  • 拥有一个保存类的实例的静态成员变量
  • 拥有一个访问这个实例的公共的静态方法(常用getInstance()方法进行实例化单例类,通过instanceof操作符可以检测到类是否已经被实例化)

简单的记为三私一公一关键:

  • 私有静态属性(privite static $instance),又来储存生成的唯一对象
  • 私有构造函数 (privite __contruct())
  • 私有克隆函数(privite function __clone()),防止克隆——clone
  • 公共静态方法(public static function getInstance()),用来访问静态属性储存的对象,如果没有对象,则生成此单例
  • 关键词instanceof,检查此变量是否为该类的对象、子类、或是实现接口。

三、单例模式的优点、缺点

优点

  1. 由于单例模式在内存中只有一个实例,减少内存开支,特别是一个对象需要频繁地创建销毁时,而且创建或销毁时性能又无法优化,单例模式就非常明显了。
  2. 由于单例模式只生成一个实例,所以,减少系统的性能开销,当一个对象产生需要比较多的资源时,如读取配置,产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后永久驻留内存的方式来解决。
  3. 单例模式可以避免对资源的多重占用,例如一个写文件操作,由于只有一个实例存在内存中,避免对同一个资源文件的同时写操作。
  4. 单例模式可以在系统设置全局的访问点,优化和共享资源访问,例如,可以设计一个单例类,负责所有数据表的映射处理。

缺点

  1. 单例模式一般没有接口,扩展很困难,若要扩展,除了修改代码基本上没有第二种途径可以实现。
  2. 单例对象如果持有Context,那么很容易引发内存泄漏,此时需要注意传递给单例对象的Context最好是Application Context。

四、应用场景(即什么时候要用它)

  1. 数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库资源。数据库软件系统中使用数据库连接池,主要是节省打开或者关闭数据库连接所引起的效率损耗,这种效率上的损耗还是非常昂贵的,因为何用单例模式来维护,就可以大大降低这种损耗。
  2. 多线程的线程池的设计一般也是采用单例模式,这是由于线程池要方便对池中的线程进行控制。
  3. 操作系统的文件系统,也是大的单例模式实现的具体例子,一个操作系统只能有一个文件系统。
  4. 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);
	}
	
}