PHP单例模式

时间:2019-04-16 23:59:04  来源:igfitidea点击:

设计模式是对软件开发中常见问题的经过良好测试的解决方案。
让我们使用逐步解决问题的方法来实现PHP中最常见的单例模式。

需求场景

在PHP开发(尤其是使用MVC框架)中,我们经常需要include/require一个DB类、一个Upload类或一个Cookie类,如下所示:

<?php

require 'DB.class.php';

require 'Upload.class.php';

require 'Cookie.class.php';

但是我们需要确保只有一个DB类或者Cookie类的实例,因为一个实例就足够了,而更多的实例就有问题了。这时单例模式就出现了。

简单地说,单例模式意思是如果一个程序员已经实例化了某个类的一个对象,那么另一个程序员就不能实例化第二个对象。

实现

创建一个通用类

创建了一个名为Singleton的类,就可以实例化任意数量的对象。这里我们创建两个Singleton类对象:

class Singleton {

   
}


$s1 = new Singleton();

$s2 = new Singleton();

$s1和$s2 是两个不同的对象。
我们可以通过使用一个简单的if()语句来判断:

class Singleton {

   
}


$s1 = new Singleton();

$s2 = new Singleton();


if ($s1 === $s2) {

    echo 's1和s2是同一个对象';

} else {

    echo 's1和s2不是同一个对象';

}

禁止new对象操作

对象是由类的构造函数创建的。假如我们把构造函数设置为protected,隐藏在类本身中,这样外部就不能使用它了,但是出现新问题,第一个对象也不能创建了:

class Singleton2 {


    protected function __construct() {

    }


}


$s3 = new Singleton2(); 
//Fatal error: Call to protected Singleton2::__construct() from invalid context

创建一个方法用于在内部创建对象

为了让类用户创建一个对象,我们需要留下一个对象创建“接口”,所以我们添加了一个名为getIns()的新方法,它是公共的(对外开放)和静态的(所以可以使用类名来调用它):

class Singleton3 {


    public static function getIns() {

        return new self();

    }


    protected function __construct() {


    }


}

我们在类中创建一个方法。在getIns()中,我们创建类本身的一个对象,然后将它返回。现在我们再次测试:

class Singleton3 {


    public static function getIns() {

        return new self();

    }


    protected function __construct() {

    }


}


$s4 = Singleton3::getIns();

$s5 = Singleton3::getIns();


if ($s4 === $s5) {

    echo 's4 和 s5是同一个对象';

} else {

    echo 's4 和 s5不是同一个对象';

}

结果还是s4 和 s5不是同一个对象

为什么?因为Singleton3::getIns()被调用两次,因此创建了两个对象。

修改getIns()方法

class Singleton4 {


    protected static $ins = NULL;


    public static function getIns() {

        if (self::$ins === null) {

            self::$ins = new self();

        }


        return self::$ins;

    }


    protected function __construct() {


    }


}


$s6 = Singleton4::getIns();

$s7 = Singleton4::getIns();


if ($s6 === $s7) {

    echo 's6 and s7 are the same object';

} else {

    echo 's6 and s7 are NOT the same object';

}

结果 s6 and s7 are the same object
可以看到,现在我们将类的实例存储在名为$ins的受保护的属性中。然后,当调用getIns()时,我们进行检查:如果$ins为空,那么我们实例化一个类对象。最后,返回self::$ins。

现在这个类现在只有一个实例。

新问题,如果有另一个类叫Multi,它继承了我们原来的类:

class Singleton4 {


    protected static $ins = NULL;


    public static function getIns() {

        if (self::$ins === null) {

            self::$ins = new self();

        }


        return self::$ins;

    }


    protected function __construct() {

        
    }


}


class Multi extends Singleton4 {

    public function __construct() {
      
    }

}


$s6 = Singleton4::getIns();

$s7 = Singleton4::getIns();


if ($s6 === $s7) {

    echo 's6 and s7 are the same object';

} else {

    echo 's6 and s7 are NOT the same object';

}


echo '<br>';

$s8 = new Multi();

$s9 = new Multi();

if ($s8 === $s9) {

    echo 's8 and s9 are the same object';

} else {

    echo 's8 and s9 are NOT the same object';

}

在这个子类中,父构造函数的可见性变为public:

s6 and s7 are the same object
s8 and s9 are NOT the same object

原因是这行代码 public function __construct() {}破坏了可见性。
所以我们需要防止子类改变父类构造函数的可见性。

防止子类重写父类构造函数

在父构造函数前面添加关键字final:

class Singleton5 {


    protected static $ins = NULL;


    public static function getIns() {

        if (self::$ins === null) {

            self::$ins = new self();

        }


        return self::$ins;

    }


    // 添加final,所以这个方法不能被覆盖!

    final protected function __construct() {


    }


}

新问题,如果类的使用者使用clone方法:

class Singleton5 {


    protected static $ins = NULL;


    public static function getIns() {

        if (self::$ins === null) {

            self::$ins = new self();

        }


        return self::$ins;

    }


    // 添加final,所以这个方法不能被覆盖!

    final protected function __construct() {

        
    }


}


$s10 = Singleton5::getIns();

$s11 = clone $s10;  // s11 克隆 s10, 新的对象创建了


if ($s10 === $s11) {

    echo 's10 and s11 are the same object';

} else {

    echo 's10 and s11 are NOT the same object';

}

结果 s10 and s11 are NOT the same object又有了多个实例

禁止使用_clone()魔术函数

class Singleton6 {


    protected static $ins = NULL;


    public static function getIns() {

        if (self::$ins === null) {

            self::$ins = new self();

        }


        return self::$ins;

    }


    // 添加final,所以这个方法不能被覆盖!

    final protected function __construct() {


    }

   

    // 禁止克隆

    final protected function __clone(){

 
    }


}


$s10 = Singleton6::getIns();

$s11 = clone $s10;  // Fatal error: Call to protected Singleton6::__clone()

这就是我们最后完成了单例模式!