大话PHP设计模式-单例模式

设计模式 单列模式

常见的设计模式大致有20多种,根据使用目标的不同可以分为以下三大类:

  • 创建模式:用于创建对象从而将某个对象从实现中解耦合。
  • 架构模式:用于在不同的对象之间构造大的对象结构。
  • 行为模式:用于在不同的对象之间管理算法、关系以及职责

单例模式

单例模式(Singleton pattern)属于创建模式的一种,它的作用在于限制程序,使其只能创建某一个特定类型单一的实例。举个简单的例子,在操作数据库对象的时候,我们想在一次请求里面只创建一次数据库的链接就可以在应用全局中使用,因为创建多个数据库链接也是对连接资源的浪费,那么这个我们可以用单例模式来实现。

在很多场景下会用到,如:配置类、Session类、Database类、Cache类、File类等等。
这些只需要实例化一次,就可以在应用全局中使用。

1、首先抛出一个问题

如果没有使用单例模式,会有什么样的问题? 如下是一个简单的数据库连接类,它没有使用单例模式

定义一个数据库连接类
<?php
# 定义一个数据库连接类
class Database  
{  
    # 定义数据库配置
    private $config = array(  
        'db_name' => 'test',  
        'db_host' => 'localhost',  
        'db_user' => 'root',  
        'db_pass' => 'root'  
    );  
    public $db = null;  
    public function __construct()  
    {  
        $dsn = sprintf('mysql:host=%s;dbname=%s', $this->config['db_host'], $this->config['db_name']);  
        $this->db = new PDO($dsn, $this->config['db_user'], $this->config['db_pass']);  
    }  
}

# 实例化数据库连接类
$db1 = new Database();  
var_dump($db1);echo '<br />';  
$db2 = new Database();  
var_dump($db2);echo '<br />';  
$db3 = new Database();  
var_dump($db3);echo '<br />';  

?>

我们运行该文件,看一下输出结果:

object(Database)#1 (1) { ... ["db"]=> object(PDO)#2 (0) { } }   
object(Database)#3 (1) { ... ["db"]=> object(PDO)#4 (0) { } }   
object(Database)#5 (1) { ... ["db"]=> object(PDO)#6 (0) { } }   

这种情况下,每当我们创建一个这个类的实例,就会新增一个到数据库的连接
开发者每在一个地方实例化一次这个类,就会在那里多一个数据库连接
不知不觉中,开发者就犯了个错误,给数据库和服务器性能带来巨大的影响
每个对象都分配一个新的资源ID,都是新的引用,它们占用3个的内存空间
如果有100个对象创建,就会占用内存中100块不同的空间,而其余99块并非是必须的,而我们真正需要一个实例就够了,这样就大大的浪费了资源。

2、单例模式来解决问题

类似操作数据库连接这种情况,我们可以控制住相关基类成为一个单例模式,在源头上限制这个资源,使其无法生成多个对象,如果已经生成过,直接返回。这样既可以提高开发效率,又有利于后期维护.

class Database
{
    // 声明$instance为私有静态类型,用于保存当前类实例化后的对象
    private static $instance = null;
    // 数据库连接句柄
    private $db = null;

    // 构造方法声明为私有方法,禁止外部程序使用new实例化,只能在内部new
    private function __construct($config = array())
    {
        $dsn = sprintf('mysql:host=%s;dbname=%s', $config['db_host'], $config['db_name']);
        $this->db = new PDO($dsn, $config['db_user'], $config['db_pass']);
    }

    // 这是获取当前类对象的唯一方式
    public static function getInstance($config = array())
    {
        // 检查对象是否已经存在,不存在则实例化后保存到$instance属性
        if(self::$instance == null) {
            self::$instance = new self($config);
        }
        return self::$instance;
    }

    // 获取数据库句柄方法
    public function db()
    {
        return $this->db;
    }

    // 声明成私有方法,禁止克隆对象
    private function __clone(){}
    // 声明成私有方法,禁止重建对象
    private function __wakeup(){}
}

再通过公共的静态方法 getInstance()方法使用类对象,

$config = array(
    'db_name' => 'test',
    'db_host' => 'localhost',
    'db_user' => 'root',
    'db_pass' => 'root'
);

$db1 = Database::getInstance($config);
var_dump($db1);
$db2 = Database::getInstance($config);
var_dump($db2);
$db3 = Database::getInstance($config);
var_dump($db3);

运行结构如下:

object(Database)#1 (1) { ["db":"Database":private]=> object(PDO)#2 (0) { } }
object(Database)#1 (1) { ["db":"Database":private]=> object(PDO)#2 (0) { } }
object(Database)#1 (1) { ["db":"Database":private]=> object(PDO)#2 (0) { } }

对比两个输出可以看出,单例模式中,不同对象获得的资源ID是一样的

也就是说,虽然我们用getInstance()获取Database类对象3次,其实引用的是一个内存空间,PDO也只连接了数据库一次

以上的例子是数据库连接类,要使用数据库,在应用这样获得连接句柄
(注意:静态方法调用的时候,不会先执行构造方法,只是会运行该静态方法)

$db = database::getInstance($config)->db();

如果是其他类,则按需要修改数据库相关的代码,单例实现部分保留。

3、总结

单例模式的特点是4私1公:一个私有静态属性,构造方法私有,克隆方法私有,重建方法私有,一个公共静态方法

其他方法根据需要增加。

最基础的单例模式代码如下:

class Singleton
{
    private static $instance = null;

    public static function getInstance()
    {
        if(self::$instance == null) {
            self::$instance = new self();
        }
        return self::$instance;
    }

    private function __construct(){}
    private function __clone(){}
    private function __wakeup(){}
}

$instance用以保存类的实例化,getInstance()方法提供给外部本类的实例化对象

从以上代码中,我们总结出PHP单例模式实现的核心要点有如下三条:

1.需要一个保存类的唯一实例的静态成员变量(通常为$_instance私有变量)

2.构造函数和克隆函数必须声明为私有的,这是为了防止外部程序new类从而失去单例模式的意义

3.必须提供一个访问这个实例的公共的静态方法(通常为getInstance方法),从而返回唯一实例的一个引用

大家想一想有没有更加高级的方法来可以来代替单例那?(当然有依赖注入嘛,后面咱们在一起讨论)