php单例模式

来源:互联网 发布:贵州省大数据公需科目 编辑:程序博客网 时间:2024/05/16 05:55
单例模式,正如其名,允许我们创建一个而且只能创建一个对象的类。
这在整个系统的协同工作中非常有用,特别明确了只需一个类对象的时候。
那么,为什么要实现这么奇怪的类,只实例化一次?
在很多场景下会用到,如:配置类、Session类、Database类、Cache类、File类等等
这些只需要实例化一次,就可以在应用全局中使用。
本文我们以数据库类为例。

1 问题

如果没有使用单例模式,会有什么样的问题?
如下是一个简单的数据库连接类,它没有使用单例模式
# 定义一个数据库连接类
class Database{    public $db = null;    public 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']);    }}
# 定义数据库配置
$config = array(    'db_name' => 'weixin',    'db_host' => 'localhost',    'db_user' => 'root',    'db_pass' => 'root');
# 实例化数据库连接类
$db1 = new Database($config);var_dump($db1);echo '<br />';$db2 = new Database($config);var_dump($db2);echo '<br />';$db3 = new Database($config);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{    private $db = null;    private static $dbSession = null;    public 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 getDbSession($config = array())   {# 检查对象是否已经存在,不存在则实例化后保存到$dbSession属性中if (self::$dbSession == null) {self::$dbSession = new self($config);}return self::$dbSession;   }

/*定义数据库配置*/

$config = array(    'db_name' => 'weixin',    'db_host' => 'localhost',    'db_user' => 'root',    'db_pass' => 'root');/*实例化数据库连接类*/$db1 = Database::getDbSession($config);var_dump($db1);echo '<br />';$db2 = Database::getDbSession($config);var_dump($db2);echo '<br />';$db3 = Database::getDbSession($config);var_dump($db3);echo '<br />';
我们运行该文件,看一下输出结果:
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) { } } 
这种情况下,每当我们想要去做数据库连接的时候,我们都去调用getDbSession()方法,在这个方法中我们会去判断self::dbSession这个属性是否存在,如果存在直接返回,如果不存在我们再去做实例化连接,这样就保证了我们在多出连接数据库时,只产生一个连接,不会出现资源浪费的问题。
2 总结
单例模式的特点是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方法),从而返回唯一实例的一个引用
缺点
PHP语言是一种解释型的脚本语言,这种运行机制使得每个PHP页面被解释执行后,所有的相关资源都会被回收。也就是说,PHP在语言级别上没有办法让某个对象常驻内存,这和asp.net、Java等编译型是不同的,比如在Java中单例会一直存在于整个应用程序的生命周期里,变量是跨页面级的,真正可以做到这个实例在应用程序生命周期中的唯一性。然而在PHP中,所有的变量无论是全局变量还是类的静态成员,都是页面级的,每次页面被执行时,都会重新建立新的对象,都会在页面执行完毕后被清空,这样似乎PHP单例模式就没有什么意义了,所以PHP单例模式我觉得只是针对单次页面级请求时出现多个应用场景并需要共享同一对象资源时是非常有意义的
单例模式在应用请求的整个生命周期中都有效,这点类似全局变量,会降低程序的可测试性。
大部分情况下,也可以用依赖注入来代替单例模式,避免在应用中引入不必要的耦合
所以,对于仅需生成一个对象的类,首先考虑用依赖注入方式,其次考虑用单例模式来实现
依赖注入相关请参考其他文章

更多设计模式文章请点击下面链接:
【设计模式概述】http://blog.csdn.net/bk_guo/article/details/73828064
【简单工厂模式】http://blog.csdn.net/bk_guo/article/details/73849317
【单例模式】http://blog.csdn.net/bk_guo/article/details/73845244
【工厂方法模式】http://blog.csdn.net/bk_guo/article/details/73896262