单例设计模式(Singleton Design Pattern)理解起来非常简单。一个类只允许创建一个对象(或者实例),那这个类就是一个单例类,这种设计模式就叫作单例设计模式,简称单例模式。

单例模式解决了哪些问题:

  1. 保证一个类只有一个实例。 为什么会有人想要控制一个类所拥有的实例数量? 最常见的原因是控制某些共享资源 (例如数据库或文件) 的访问权限。
    它的运作方式是这样的: 如果你创建了一个对象, 同时过一会儿后你决定再创建一个新对象, 此时你会获得之前已创建的对象, 而不是一个新对象。

注意, 普通构造函数无法实现上述行为, 因为构造函数的设计决定了它必须总是返回一个新对象。

  1. 为该实例提供一个全局访问节点。 还记得我们用过的那些存储重要对象的全局变量吗? 它们在使用上十分方便, 但同时也非常不安全, 因为任何代码都有可能覆盖掉那些变量的内容, 从而引发程序崩溃。
    和全局变量一样, 单例模式也允许在程序的任何地方访问特定对象。 但是它可以保护该实例不被其他代码覆盖。

还有一点: 你不会希望解决同一个问题的代码分散在程序各处的。 因此更好的方式是将其放在同一个类中, 特别是当其他代码已经依赖这个类时更应该如此。

如何实现单例模式?

所有单例的实现都包含以下两个相同的步骤:

  • 将默认构造函数设为私有, 防止其他对象使用单例类的 new运算符。
  • 新建一个静态构建方法作为构造函数。 该函数会 “偷偷” 调用私有构造函数来创建对象, 并将其保存在一个静态成员变量中。 此后所有对于该函数的调用都将返回这一缓存对象。

如果你的代码能够访问单例类, 那它就能调用单例类的静态方法。 无论何时调用该方法, 它总是会返回相同的对象。

单例模式结构

示例:

<?php
namespace DesignPatterns\Singleton;
/**
 * 单例模式
 * Class Singleton
 * @package DesignPatterns\Singleton
 */
class Singleton{
    /**
     * 声明一个静态属性用于储存实例
     * @var null
     */
    private static $instances = null;

    /**
     * 声明一个私有或受保护的构造方法,防止使用new运算符直接进行调用
     */
    protected function __construct()
    {
      //
    }

    /**
     * 防止克隆多个实例
     */
    private function __clone()
    {
        // TODO: Implement __clone() method.
    }

    /**
     * 防止反序列化单例
     * @throws \Exception
     */
    public function __wakeup()
    {
        throw new \Exception("Cannot unserialize a singleton.");
    }

    /**
     *
     */
    public static function getInstance()
    {
        if (!isset(self::$instances)) {
            self::$instances = new static;
        }
        return self::$instances;
    }
}
//client
function clientCode()
{
    $s1 = Singleton::getInstance();
    $s2 = Singleton::getInstance();
    if ($s1 === $s2) {
        echo "两个变量的实例相同";
    } else {
        echo "两个变量的实例不相同";
    }
}
clientCode();

执行结果:

应用场景

单例模式适合应用场景

  • 如果程序中的某个类对于所有客户端只有一个可用的实例, 可以使用单例模式。
    单例模式禁止通过除特殊构建方法以外的任何方式来创建自身类的对象。 该方法可以创建一个新对象, 但如果该对象已经被创建, 则返回已有的对象。
  • 如果你需要更加严格地控制全局变量, 可以使用单例模式。
    单例模式与全局变量不同, 它保证类只存在一个实例。 除了单例类自己以外, 无法通过任何方式替换缓存的实例。

请注意, 你可以随时调整限制并设定生成单例实例的数量, 只需修改 获取实例方法, 即 getInstance 中的代码即可实现。

单例模式最常见的应用场景就是数据库操作了,在不使用的单例模式的情况下,程序中出现大量数据库操作时,每次都要执行new操作,每次都会消耗大量的内存资源和系统资源,而且每次打开和关闭数据库连接都是对数据库的一种极大考验和浪费。使用了单例模式,只需要实例化一次,不需要每次都执行new操作,极大降低了资源的耗费。

<?php
namespace DesignPatterns\Singleton;
class DB{
    /**
     * 声明一个静态属性用于储存实例
     * @var null
     */
    private static $instances = null;

    //数据库连接句柄
    private $db;
    //数据库连接参数
    const HOSTNAME = "127.0.0.1";
    const USERNAME = "root";
    const PASSWORD = "root";
    const DBNAME = "user";

    /**
     * 声明一个私有或受保护的构造方法,防止使用new运算符直接进行调用
     */
    private function __construct()
    {
        $this->db = mysqli_connect(self::HOSTNAME,self::USERNAME,
            self::PASSWORD,self::DBNAME);
    }

    /**
     * 防止克隆多个实例
     */
    private function __clone()
    {
        // TODO: Implement __clone() method.
    }

    /**
     * 防止反序列化单例
     * @throws \Exception
     */
    public function __wakeup()
    {
        throw new \Exception("Cannot unserialize a singleton.");
    }

    /**
     *单例模式访问入口
     */
    public static function getInstance()
    {
        if (!isset(self::$instances)) {
            self::$instances = new static;
        }
        return self::$instances;
    }

    /**
     * 数据库查询
     */
    public function select(){
        $sql = "select * from user";
        $res = mysqli_query($this->db,$sql);

        while ($row = mysqli_fetch_array($res)) {
            printf ("ID: %s  Name: %s", $row[0], $row[1]);
        }
        mysqli_free_result($res);
    }

}
//client
$db = DB::getInstance();
$db->select();

单例模式优缺点

优点:

  • 你可以保证一个类只有一个实例。
  • 你获得了一个指向该实例的全局访问节点。
  • 仅在首次请求单例对象时对其进行初始化。

缺点:

  • 违反了单一职责原则。 该模式同时解决了两个问题。
  • 单例模式可能掩盖不良设计, 比如程序各组件之间相互了解过多等。
  • 该模式在多线程环境下需要进行特殊处理, 避免多个线程多次创建单例对象。
  • 单例的客户端代码单元测试可能会比较困难, 因为许多测试框架以基于继承的方式创建模拟对象。 由于单例类的构造函数是私有的, 而且绝大部分语言无法重写静态方法, 所以你需要想出仔细考虑模拟单例的方法。 要么干脆不编写测试代码, 或者不使用单例模式。

githubhttps://github.com/yangpanyao/design-patterns/tree/master/Singleton

Last modification:June 8th, 2020 at 03:02 pm