我都对自己有点无语了,又要开始写单例模式,都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方面,最近又有了新的实践心得。争取这个月写出来。
感觉这段时间,写起来越来越慢了,难道是写的越来越好了?哈哈