PHP设计模式----单例模式(singleton)

时间:2022-12-11 12:14:29

提出问题:
为什么使用单例模式?

对于系统中的某些类来说,只有一个实例很重要,例如,1、一个系统中可以存在多个打印任务,但是只能有一个正在工作的任务;2、在Windows中就只能打开一个任务管理器。如果不使用机制对窗口对象进行唯一化,将弹出多个窗口,如果这些窗口显示的内容完全一致,则是重复对象,浪费内存资源;如果这些窗口显示的内容不一致,则意味着在某一瞬间系统有多个状态,与实际不符,也会给用户带来误解,不知道哪一个才是真实的状态,因此任务窗口必须唯一;3、我们在链接数据库的时候,在整个系统中,即使我们已经封装好一个对数据库的链接的类,但是如果有好多地方都要进行数据库的链接和操作,分别实例化这个类,那么将会产生大量的数据操作,每次都要new操作,但是每次new都会消耗大量的内存资源和系统资源,而且每次打开和关闭数据库连接都 是对数据库的一种极大考验和浪费。

因此,单例模式将是一个很好的解决方法。

单例模式有以下3个特点:

1.某个确定的类只能有一个实例。

2.必须自行创建这个实例。

3.必须给其他对象(整个系统)提供这一实例。

那么为什么要使用PHP单例模式?

PHP一个主要应用场合就是应用程序与数据库打交道的场景,在一个应用中会存在大量的数据库操作,针对数据库句柄连接数据库的行为,使用单例模式可以避免大量的new操作。因为每一次new操作都会消耗系统和内存的资源。

如下代码:

<?php  
class MysqlConn
{

// MYSQL数据库连接信息
const MYSQLHOSTNAME = "127.0.0.1";
const MYSQLUSERNAME = "root";
const MYSQLPASSWORD = "123456";
const MYSQLDBNAME = "test";
const MYSQLCHARSET = "utf8";

/**
* Description:mysql数据库连接函数
* Return value:连接成功返回数据库连接句柄;连接失败返回错误消息
*/

public function MysqlConnect()
{

$db = new mysqli(self::MYSQLHOSTNAME, self::MYSQLUSERNAEM, self::MYSQLPASSWORD, self::MYSQLDBNAME); // 连接数据库
$db->set_charset(self::MYSQLCHARSET);
if (mysqli_connect_errno())
{
throw new CircleMysqlException("服务器系统故障", 1001);
}
else
{
return $db;
}
}
}
?>

缺陷:
每次数据库连接都要new这个类,然后调用mysqlconnect方法,返回close掉,频繁的new和数据库连接关闭操作是非常消耗资源的

改进:
每次应该直接返回当前应用中已经打开的数据库连接句柄,如果关闭了,再重新打开。

这里使用单例模式如下:

<?php  
class Singleton
{
/**
* Description:(1)静态变量,保存全局实例,跟类绑定,跟对象无关
* (2)私有属性,为了避免类外直接调用 类名::$instance,防止为空
*/

private static $instance;

/**
* Description:数据库连接句柄
*/

private $db; //可由前面的类获取得到或者整合进来

/**
* Description:私有化构造函数,防止外界实例化对象(如果外界能实例化,那么也会产生大量的数据库操作)
*/

private function __construct()
{
}

/**
* Description:私有化克隆函数,防止外界克隆对象
*/

private function __clone()
{
}

/**
* Description:静态方法,单例访问统一入口
* @return Singleton:返回应用中的唯一对象实例
*/

public static function GetInstance()
{
if (!(self::$instance instanceof self))
{
self::$instance = new self();
}
return self::$instance;
}

/**
* Description:获取数据库的私有方法的连接句柄
*/

public function GetDbConnect()
{
return $this->db;
}
}
  • 需要一个保存类的唯一实例的静态成员变量(通常$instance为私有变量)
  • 构造函数和克隆函数必须声明为私有的,为了防止外部程序new类从而失去单例模式意义
  • 必须提供一个访问这个实例的公共静态方法,从而返回唯一实例的一个引用

以上内容参考自http://www.cnblogs.com/lh460795/archive/2013/07/30/3225650.html

再举一个例子:
假设现在我们需要一个类用来保存应用程序的程序信息,比如作者,电话号码,地址等,这些信息在你每次部署程序时都会有所不同。该对象也可被用作一个“公告板”,它是系统中的其他无关对象设置和获取消息的中心。比如A这个对象负责设置作者,B这个对象负责设置地址,C这个对象可能要得到该应用程序的作者和电话号码。
那么问题来了,假如这个类,明确是这个对象,如果对于A、B、C、不是唯一的,即它们操作的对象不一样,那就会出现作者不唯一,电话号码不唯一,C获取到的信息是空白的。因此这里我们就要用到单例模式了:

<?php
class Singleton{
private $props = array();//用来保存作者,电话号码,地址等

private static $instance;//该类的实例,私有防止被外界使用

private function __construct(){ }//构造函数私有,防止在外部被实例化

private function __clone(){ }//防止被克隆产生相同的对象

public static function getInstance(){

if(!self::$instance instanceof self){

self::$instance = new self();

}

return self::$instance;
}

//设置对象属性
public function setProperty($key,$val){

$this->props[$key] = $val;

}

//获取对象属性
public function getProperty($key){

return $this->props[$key];

}
}

好了,现在我们使用一下这个类是否满足我们的需求

$pref = Singleton::getInstance();

$pref->setProperty('name','LSGO实验室');

unset($pref);//销毁引用

$pref2 = Singleton::getInstance();

echo $pref2 = getProperty('name');

//最终返回LSGO实验室,而不是空白,证明$pref$pref2操作的是同一个对象

单例模式就介绍完了,其实一开始我想,假如我用一个全局变量来保存这个特定的对象,然后在任意的一个无关对象里面也能实现这些功能啊,但是,全局变量是不安全的,你能保证在其他地方不会存在同名的全局变量把该变量覆盖了??在面向对象的开发环境中,单例模式是一种对于全局变量的改进。

后半部分参考自《深入PHP面向对象、模式与实践》一书和LSGO团队老大给我的讲解