PHP设计模式之单例模式

单例设计模式(Singleton Design Pattern)的意思是:一个类只允许创建一个对象(或者实例),那这个类就是一个单例类,这种设计模式就叫作单例模式。单例模式可以保证类的对象全局唯一

  • 场景一:处理资源访问冲突。例如:多线程的日志写入。
  • 场景二:表示全局唯一类。从业务概念上,如果有些数据在系统中只应保存一份,那就比较适合设计为单例类。比如,配置信息类。再比如,唯一递增 ID 号码生成器。

要实现一个单例,需要关注的点有下面几个:

  • 构造函数需要是 private 访问权限的,这样才能避免外部通过 new 创建实例;
  • 考虑对象创建时的线程安全问题;
  • 考虑是否支持延迟加载;
  • 考虑 getInstance() 性能是否高(是否加锁)。

单例模式又分为饿汉式单例模式和懒汉式单例模式:

  • 饿汉式单例模式:在类加载的时候,instance 静态实例就已经创建并初始化好了,所以,instance 实例的创建过程是线程安全的。不过,这样的实现方式不支持延迟加载实例。采用饿汉式实现方式,将耗时的初始化操作,提前到程序启动的时候完成,这样就能避免在程序运行的时候,再去初始化导致的性能问题。
  • 懒汉式单例模式:相对于饿汉式的优势是支持延迟加载。这种实现方式会导致频繁加锁、释放锁,以及并发度低等问题,频繁的调用会产生性能瓶颈。

接下来使用PHP代码一步一步分析单例模式的实现。

/**
 * 普通的可以调用的类
 * 先定义一个类,实例化两次,看看是否全等(当两个对象是一个的时候才会全等)
 */
class ObjectA {

}

$objA1 = new ObjectA();
$objA2 = new ObjectA();
var_dump($objA1 === $objA2); //bool(false)

上面定义了一个普通的类,实例化两次,比较是否全等,输出false。如果我们要实现实例化两次(或者更多次)后的对象实例全等,就要用到单例模式了,改造如下:

class ObjectB {
    private static $instance = null;

    public static function getInstance() {
        if (self::$instance === null) {
            //把自身对象赋给一个自己的静态属性
            self::$instance = new self();
        }
        return self::$instance;
    }

    //私有化构造函数,禁止直接new操作
    private function __construct() {

    }
}

$objB1 = ObjectB::getInstance();
$objB2 = ObjectB::getInstance();
var_dump($objB1 === $objB2); //bool(true)

注意上面对构造函数私有化。此时,实例化两次之后发现它们是全等的。但是还是存在一个问题,看下面的代码:

$objB3 = clone $objB1;
var_dump($objB1 === $objB3); //bool(false)

对其中一个实例进行克隆之后,判断全等还是false,因此还需要对单例类禁止克隆。改造如下:

class ObjectC {
    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() {

    }
}

$objC1 = ObjectC::getInstance();
$objC2 = ObjectC::getInstance();
var_dump($objC1 === $objC2); //bool(true)

$objC3 = clone $objC1; //此处报错:Fatal error: Uncaught Error: Call to private ObjectC::__clone() from context ''
var_dump($objC1 === $objC3); //程序不会执行到这里

因此,上面的代码就实现了一个单例模式。

深入分析单例模式:
        “一个类只允许创建唯一一个对象”,那对象的唯一性的作用范围是什么呢? 是指线程内只允许创建一个对象,还是指进程内只允许创建一个对象? 答案是后者,也就是说,单例模式创建的对象是进程唯一的
        如何实现线程唯一的单例? 我们通过一个 HashMap 来存储对象,其中 key 是线程 ID,value 是对象。这样我们就可以做到,不同的线程对应不同的对象,同一个线程只能对应一个对象。在Java语言本身提供了 ThreadLocal 并发工具类,可以更加轻松地实现线程唯一单例。
        如何实现集群环境下的单例? 我们需要把这个单例对象序列化并存储到外部共享存储区(比如文件)。进程在使用这个单例对象的时候,需要先从外部共享存储区中将它读取到内存,并反序列化成对象,然后再使用,使用完成之后还需要再存储回外部共享存储区。

参考源代码:https://gitee.com/rxbook/php_design_pattern/blob/master/code05_Singleton.php

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

浮尘笔记

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值