领先的免费Web技术教程,涵盖HTML到ASP.NET

网站首页 > 知识剖析 正文

PHP 单例模式

nixiaole 2025-04-27 15:30:03 知识剖析 3 ℃

单例模式(Singleton Pattern)

单例模式(Singleton Pattern):顾名思义, 就是只有一个实例。作为对象的创建模式, 单例模式确保某一个类只有一个实例, 而且自行实例化并向整个系统提供这个实例。

(一)为什么要使用PHP单例模式

1, PHP的应用主要在于数据库应用, 一个应用中会存在大量的数据库操作, 在使用面向对象的方式开发时, 如果使用单例模式, 则可以避免大量的new 操作消耗的资源,

还可以减少数据库连接这样就不容易出现 too many connections情况。

2, 如果系统中需要有一个类来全局控制某些配置信息, 那么使用单例模式可以很方便的实现。 这个可以参看zend Framework的FrontController部分。

3, 在一次页面请求中, 便于进行调试, 因为所有的代码(例如数据库操作类db)都集中在一个类中, 我们可以在类中设置钩子, 输出日志, 从而避免到处var_dump, echo

单例模式的实现

1, 私有化一个属性用于存放唯一的一个实例

2, 私有化构造方法, 私有化克隆方法, 用来创建并只允许创建一个实例

3, 公有化静态方法, 用于向系统提供这个实例

<?php
class Singleton{
    //存放实例
    private static $_instance = null;
    //私有化构造方法、
    private function __construct(){
        echo "单例模式的实例被构造了";
    }
    //私有化克隆方法
    private function __clone(){
    }
    //公有化获取实例方法
    public static function getInstance(){
        if (!(self::$_instance instanceof Singleton)){
        self::$_instance = new Singleton();
        }
    		return self::$_instance;
    }
}
$singleton=Singleton::getInstance();
?>
<?php
class Single {
    /**
    * @var Object 保存类实例的静态成员变量
    */
    private static $_instance;
    /**
    * Single constructor. 私有的构造方法
    */
    private function __construct(){
        echo 'This is a Constructed method;';
    }
    /**
    * @purpose: 创建__clone方法防止对象被复制克隆
    */
    public 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 '调用方法成功';
    }
}
?>


优点:因为静态方法可以在全局范围内被访问, 当我们需要一个单例模式的对象时, 只需调用getInstance方法, 获取先前实例化的对象, 无需重新实例化。

(二)使用Trait关键字实现类似于继承单例类的功能

Trait Singleton{
    //存放实例
    private static $_instance = null;
    //私有化克隆方法
    private function __clone(){
    }
    //公有化获取实例方法
    public static function getInstance(){
        $class = __CLASS__;
        if (!(self::$_instance instanceof $class)){
            self::$_instance = new $class();
        }
        return self::$_instance;
    }
    }
    class DB {
        private function __construct(){
            echo __CLASS__.PHP_EOL;
        }
    }
class DBhandle extends DB {
    use Singleton;
    private function __construct(){
        echo "单例模式的实例被构造了";
    }
}
$handle=DBhandle::getInstance();

//注意若父类方法为public,则子类只能为pubic,若父类为private,子类为public ,protected,private都可以。

自 PHP 5.4.0 起, PHP 实现了代码复用的一个方法, 称为 traits。

Traits 是一种为类似 PHP 的单继承语言而准备的代码复用机制。Trait 为了减少单继承语言的限制, 使开发人员能够自由地在不同层次结构内独立的类中复用方法集。

Traits 和类组合的语义是定义了一种方式来减少复杂性, 避免传统多继承和混入类(Mixin)相关的典型问题。

补充, 大多数书籍介绍单例模式, 都会讲三私一公, 公优化静态方法作为提供对象的接口, 私有属性用于存放唯一一个单例对象。私有化构造方法, 私有化克隆方法保证只存在一个单例。

但实际上, 虽然我们无法通过 new 关键字和clone出一个新的对象, 但我们若想得到一个新对象。

还是有办法的, 那就是通过序列化和反序列化得到一个对象。私有化sleep()和wakeup()方法依然无法阻止通过这种方法得到一个新对象。

或许真得要阻止, 你只能去__wakeup添加删除一个实例的代码, 保证反序列化增加一个对象, 你就删除一个。不过这样貌似有点怪异。

(三) PHP单例模式及应用场景

单例模式在数据库链接中的使用:

<?php
class Mysql
{
    // MYSQL数据库连接信息
    const HOSTNAME = "127.0.0.1";
    const USERNAME = "root";
    const PASSWORD = "***";
    const DBNAME = "test";
    const CHARSET = "utf8";

    public function MysqlConnect()
    {
        $db = new mysqli(self::HOSTNAME, self::USERNAEM, self::PASSWORD, self::DBNAME); // 连接数据库
        $db->query("set names ".self::CHARSET);
        if (mysqli_connect_errno())
        {
        		throw new MysqlException("服务器系统故障", 1001);
        }
        else
        {
        		return $db;
        }
    }
    }
?>

每次数据库连接都要new这个类, 然后调用mysqlconnect方法, 返回连接, 然后close掉连接, 频繁的new和数据库连接关闭操作非常的消耗资源!数据库软件系统中使用数据库连接池,

主要是节省打开或者关闭数据库连接所引起的效率损耗, 这种效率上的损耗还是非常昂贵的, 因为使用单例模式来维护, 就可以大大降低这种损耗。

因此, 为了避免资源消耗, 我们有了下面的改进:

<?php
class Mysql{
    //该属性用来保存实例
    private static $conn;
    //构造函数为private,防止创建对象
    private function __construct(){
        $this->conn = mysql_connect('localhost','root','');
    }
    //创建一个用来实例化对象的方法
    public static function getInstance(){
        if(!(self::$conn instanceof self)){
            self::$conn = new self;
        }
        return self::$conn;
    }
    //防止对象被复制
    public function __clone(){
        trigger_error('Clone is not allowed !');
    }

}
//只能这样取得实例,不能new 和 clone
$mysql = Mysql::getInstance();
?>

这样就不用每次都来new这个类了, 方便了很多。下面给一个详细的:

<?php
class Mysql
{
private $DB;
static private $_instance;
// 连接数据库
private function __construct($host, $username, $password)
{
    $this->DB = mysql_connect($host, $username, $password);
    $this->query("SET NAMES 'utf8'", $this->link);
    return $this->DB;
}

private function __clone(){}

public static function getInstance($host, $username, $password)
{
    if( !(self::$_instance instanceof self) )
    {
    		self::$_instance = new self($host, $username, $password);
    }
    return self::$_instance;
}

// 连接数据表
public function select_db($database)
{
    $this->result = mysql_select_db($database);
    return $this->result;
}

// 执行SQL语句
public function query($query)
{
		return $this->result = mysql_query($query, $this->link);
}

// 将结果集保存为数组
public function fetch_array($fetch_array)
{
		return $this->result = mysql_fetch_array($fetch_array, MYSQL_ASSOC);
}

// 获得记录数目
public function num_rows($query)
{
return $this->result = mysql_num_rows($query);
}

// 关闭数据库连接
public function close()
{
		return $this->result = mysql_close($this->link);
}

}
?>

使用的时候:

$con = Mysql::getInstance($host, $username, $password);
$con -> select_db($database);


当然, 单例模式不仅仅只是应用在数据库的操作类上面。还可以应用在这些方面:

1. 网站的计数器, 一般也是采用单例模式实现, 否则难以同步。

2. 应用程序的日志应用, 一般都何用单例模式实现, 这一般是由于共享的日志文件一直处于打开状态, 因为只能有一个实例去操作, 否则内容不好追加。

3. Web应用的配置对象的读取, 一般也应用单例模式, 这个是由于配置文件是共享的资源。

PHP单例模式的缺点

众所周知, PHP语言是一种解释型的脚本语言, 这种运行机制使得每个PHP页面被解释执行后, 所有的相关资源都会被回收。也就是说, PHP在语言级别上没有办法让某个对象常驻内存,

这和asp.net、Java等编译型是不同的, 比如在Java中单例会一直存在于整个应用程序的生命周期里, 变量是跨页面级的, 真正可以做到这个实例在应用程序生命周期中的唯一性。

然而在PHP中, 所有的变量无论是全局变量还是类的静态成员, 都是页面级的, 每次页面被执行时, 都会重新建立新的对象, 都会在页面执行完毕后被清空, 这样似乎PHP单例模式就没有什么意义了,

所以PHP单例模式我觉得只是针对单次页面级请求时出现多个应用场景并需要共享同一对象资源时是非常有意义的。

最近发表
标签列表