2.php的设计模式:单例模式

我都对自己有点无语了,又要开始写单例模式,都TM是套路。
不过话虽如此,套路照打。

声明

据说当前单例模式已被列入反模式的行列了,不过我还在用,因为需要。有需求就有市场。
他的一个好处是,确保需要全局唯一的变量,不重复生成,节约空间。比如:DB,我们仅仅需要在一次生命周期中,创建一个连接就好。这个时候就很适合用单例模式。

大家都知道的单例模式代码

<?php
class MySqlDB
{
    private static $instance;

    private function __contruct()
    {}

    private function __clone()
    {}

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

        return self::$instance
    }
}

测试

<?php
    $db1 = MySqlDB::getInstance();
    $db2 = MySqlDB::getInstance();
    var_dump($db1 == $db2); // true

通过以上代码,我们可以发现,再一次执行中,无论获取多少个db实例,其实都是同一个。
这里需要注意的是 self::$instance instanceof self 这一行代码,我觉得写得还是比较得当的,我以前常常使用 empty() 这个函数来进行判断,虽然也没有问题,但是读起来就没有前面那行代码优美了。

另外这种写法有个蛋碎的缺点。那就是一个项目中需要多个单例,每个都需要写一坨 getInstance() 中的代码。想想看,是不是瞬间就不优美了?因此我们可以设计一个单例类,继承后就是一个单例了。laravel中进行容器注册确保单例,其实就是下面我要实现单例模式的思路。各位看官且继续往下看:

改良版,可以继承的单例类

abstract class Singleton
{
    // 用于保存多个实例
    protected static $instances = [];

    public static function getInstance()
    {
        $className = static::getClassName();
        // 以前写这个地方的时候,都是判断是否为null,现在改为根据类型判断,更加严谨
        if (!(self::$instances[$className] instanceof  $className)) {
            self::$instances[$className] = new $className();
        }

        return self::$instances[$className];
    }

    public static function removeInstance()
    {
        $className = static::getClassName();
        if (array_key_exists($className, self::$instances)) {
            unset(self::$instances[$className]);
        }
    }

    /**
     * 此处返回需要生成实例的类全路径,包括命名空间
     *  - 如果需要创建其他类为单例,此处返回其他类的全命名空间
     *  - 如果是让继承的类成为单例,此方法不需要重写
     * @author helei
     */
    protected function getClassName()
    {
        return get_called_class();
    }

    protected function __construct() {}

    final protected function __clone() {}
}

OK!假设现在我们有一个redis的工厂,要成为一个生成redis的单例。那么应该这么搞:

class RedisFactory extends Singleton
{
    public $redis;

    protected function __construct()
    {
        // 初始化redis及链接redis
        $this->redis = new Redis;
        $this->redis->connect();
    }
}

发现没有?发现没有?现在要实现一个单例,是如此的简单,子类只需要将注意力放在自己的具体业务上,而不用关心单例的对应规则了。这算不算【职责单一原则】?这样子,类就达到了最大程度的复用。

但是这里,需要注意几个问题。
第一、大家有没有发现,我的redis用的是public的属性;
第二、其实这个单例是RedisFactory 并不是redis本身。

基于上面的原因,比如我现在要调用redis

$factory = RedisFactory::getInstance();
$redis = $factory->redis;

$redis->set('name', 'foo');

OK,看到了吧?为了使用redis,我们必须先把工厂建好,然后工厂再把redis给我们生产出来。所以这里为了后面方便调用redis实例,所以设置成public属性。当然你完全可以复杂编码,搞成私有属性,然后写个方法来获取redis。不过有必要吗?别给我说什么规范。我不听的……

然后第二点,本身redis并不是单例,但是却达到了单例的效果,这是什么原因?
因为redis的实例化,是在RedisFactory的构造函数中完成的,而他的构造函数仅会调用一次。这就确保了redis也只会被实例化一次。

更多的问题,只能在实践中再去发现了。关于单例就写到这儿吧。下一篇对于API方面,最近又有了新的实践心得。争取这个月写出来。

感觉这段时间,写起来越来越慢了,难道是写的越来越好了?哈哈

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 猿与汪的秘密 设计师:上身试试 返回首页