65.9K
CodeProject 正在变化。 阅读更多。
Home

PHP 单例模式:分步和解决问题实现

starIconstarIconstarIconstarIconstarIcon

5.00/5 (3投票s)

2014 年 12 月 11 日

CPOL

4分钟阅读

viewsIcon

18350

从头开始设计 PHP 中的单例模式

引言

设计模式是软件开发中常见问题的经过良好测试的解决方案。在著名的“四人帮”书中,创建了 23 种设计模式,它们已广泛应用于面向对象编程中,例如 Java 和 C#。PHP 怎么样?自 PHP 5.0 以来,它已成为一种完整的面向对象语言,并且一些设计模式也已广泛应用于 PHP 及其众多的 MVC 框架中。今天,让我们使用逐步和问题解决的方法在 PHP 中实现最常见的单例模式。

场景

在 PHP 开发中(尤其是在使用 MVC 框架时),我们经常需要包含/引用一个 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 and s2 are the same object';

} else {

    echo 's1 and s2 are NOT the same object';

}

注意:在判断两个对象是否相同时,必须使用“===”,而不是“==”。

 

第二步:禁止“new”操作

如果我们想阻止类用户创建他们想要的任意数量的 Singleton 类对象,该怎么办? 嗯,你可能会认为对象是由类的构造函数创建的。 那么,我们如何将构造函数隐藏在类本身中,以便外部无法使用它呢? 就像下面的代码一样

class Singleton2 {


    protected function __construct() {

    }


}


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

现在你可以看到我们使构造函数受到保护,但现在的新问题是:没有人可以再创建对象了。

 

第三步:创建一个对象创建方法

为了让类用户可以创建一个对象,我们需要保留一个对象创建“接口”,因此我们添加了一个名为 getIns() 的新方法,它是public(对外部开放)和 static(因此可以使用类名调用它)

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 and s5 are the same object';

} else {

    echo 's4 and s5 are NOT the same object';

}

结果是:

s4 and s5 are NOT the same object

这两个对象仍然不是同一个对象,为什么? 因为 Singleton3::getIns() 被调用了两次,因此创建了两个对象。 但是现在,控制权在我们手中,我们可以改进 getIns() 以使其执行我们想要的操作。

 

第四步:改进 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';

}

你可以看到,现在我们将类的实例存储在一个名为 $ins 的受保护属性中。 然后当 getIns() 被调用时,我们会进行一些检查:如果 $ins 为 NULL,那么我们实例化一个类对象。 最后,我们返回 self::$ins。

让我们测试一下

s6 and s7 are the same object

现在我们得到了我们想要的结果! 现在类只有一个实例。 但这仍然不够,为什么? 如果有另一个名为 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;

    }


    // Adding final, so this method cannot be override!

    final protected function __construct() {


    }


}

所以现在如果我们再次运行脚本

class Singleton5 {


    protected static $ins = NULL;


    public static function getIns() {

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

            self::$ins = new self();

        }


        return self::$ins;

    }


    // Adding final, so this method cannot be override!

    final protected function __construct() {


    }


}

我们看到输出

Fatal error: Cannot override final method Singleton5::__construct()

我们完成了吗? 还没有……如果有人编写代码

class Singleton5 {


    protected static $ins = NULL;


    public static function getIns() {

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

            self::$ins = new self();

        }


        return self::$ins;

    }


    // Adding final, so this method cannot be override!

    final protected function __construct() {

        
    }


}


$s10 = Singleton5::getIns();

$s11 = clone $s10;  // s11 cloned s10, then more than one object is created!


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 关键字。 所以问题再次发生。 如何防止克隆?

 

第六步:禁止 __clone() 魔术函数

class Singleton6 {


    protected static $ins = NULL;


    public static function getIns() {

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

            self::$ins = new self();

        }


        return self::$ins;

    }


    // Adding final, so this method cannot be override!

    final protected function __construct() {


    }

   

    // Forbid clone

    final protected function __clone(){

 
    }


}


$s10 = Singleton6::getIns();

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

现在不可能克隆了。

最后,我们完成了 PHP 中的单例模式!

我希望这种逐步实现单例的方法对你有所帮助。 了解设计模式是好事,但我想,如果我们知道为什么我们需要创建这样的模式,以及如何迭代地改进我们的代码以实现我们的目标,那就更重要和有用了。

© . All rights reserved.