7分钟阅读时间 (1364个单词)

为什么老旧的Singleton模式被禁止

Why the Poor Old Singleton was Banned

曾经非常受欢迎的单例设计模式已经变成了一种反模式。从英雄到局外人。就像一个因服用兴奋剂被查的运动员。发生了什么?

让我来介绍一下我的老朋友Singleton。在学习有关设计模式的内容时,他是你最先遇到的一个。Singleton的目的,正如经典《设计模式之四人帮》(1994年)一书中所定义的

"确保一个类只有一个实例,并提供一个全局访问点。"

这通常是通过将类的构造函数和其他实例化方法(如 __clone())设为私有和最终,同时提供一个静态方法来实例化类来实现的。类持有对实例的静态引用,每次调用 getInstance() 方法时,只有在没有实例存在的情况下才会实例化类。因此,你不能调用 new YourSingletonClass,因为构造函数是私有的。当你调用 YourSingletonClass::getInstance() 时,只有在没有其他对象存在的情况下才会创建一个新的对象。

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

单例的getInstance()方法

简单、方便且聪明,不是吗?

我们都喜欢Singleton,并且在许多地方都使用了它。一个著名的例子是:当你想在应用程序中使用数据库连接时,你想要确保不会在应用程序中结束于同一个数据库的多个连接。但慢慢地,我们开始意识到这段方便的代码的缺点:我们经常用它来获取全局变量,而现在这已经成为了一个禁忌。我们可怜的Singleton被揭露成了伪装成全局变量的丑恶现实。

意大利面

我喜欢意大利菜。意大利面是保存和食用小麦的绝妙方式。我非常喜欢它摆在我盘子里,但不喜欢它在我的集成开发环境中。当代码片段紧密耦合时,维护变得像噩梦:你在一个地方更改了某些内容,突然你还得在其他地方更改其他部分。代码不应该是意大利面的样子。所以我们试图制作尽可能独立的微小构建块。这样,你可以更改一个部分,其余部分保持不变。这样的独立软件单元也可以进行测试:一个单元的所有输入都应该处于控制之下,例如通过使用临时替换(模拟)。如果你的软件中任何值的变化超出了测试的控制,你无法确定测试是正确还是错误的。当你的测试软件使用在其他地方也使用的对象时,你无法对其进行测试。因此,你需要用模拟对象替换它,模仿外部对象的行为,确保它在测试期间不会发生变化。全局变量不能被模拟,因此包含全局变量的软件片段不易进行测试。依赖关系应该更加明确。这可以通过将依赖关系注入到你的代码中来实现,例如作为类构造函数的参数。这不仅使你的软件可测试,而且在一般情况下解耦了代码片段。软件应该具有高度的聚合性但低耦合性,以提高可维护性。

伪装成全局的变量

单例模式的问题在于它们经常被用作全局变量。无论何时你可以用global $myObject替换$myObject = YourSingletonClass::getInstance(),你实际上都在用不同的语法做同样的事情。它会产生相同的问题。因此,单例通常是一个代码味道,一种反模式。依赖注入可以在许多情况下提供解决方案。顺便说一下:你不需要依赖注入容器就可以使用依赖注入,但这将是另一篇文章的内容。

人类

大多数软件开发者都有人类的一面。例如:做一些简单的事情是非常诱人的,尽管你知道这可能会在长期内产生负面影响。全局变量非常容易使用,但如果明显使用它们的方法不再被接受,我们有时会使用更隐蔽的方法来实现相同的目的。注册表、服务定位器和我们古老的单例就是这样的例子。在开发者中,你还可以找到绝对化“好”和“坏”的道德概括的人类一面。一旦单例被标记为“坏”,它就不再有任何“好”的地方:它有一个静态的(坏!)getInstance()方法,并且负责其唯一性违反(坏!)单一责任原则。我认为将某些东西绝对地标记为绝对的好或坏,是训练我们以特定方式做事的一种方式。

在软件的某个部分中仅实例化一次特定的类可能是有用的。例如,存储库模式也在做这项工作。《单例》只是《不是在软件的不同部分中使用类的单个实例的最佳方式》。这是一个伪装成全局的变量。通过依赖注入,你可以实现相同的目标,同时使你的软件更好地解耦和可测试。没有必要使用单例:如果你只想有一个类的实例,那么只实例化一次。如果你需要在多个地方使用该对象,那么就去获取它。如果你发现需要推来推去大量对象,那么你的架构可能就存在问题。

Joomla!

Joomla! CMS使用单例模式进行数据库连接。实际上,它使用的是单例的兄弟,称为多例,它是一组单例:只有具有特定签名(特定选项集)的单一数据库驱动程序可以实例化

public static function getInstance($options = array())
{
    // Sanitize the database connector options.
    // (...)

    // Get the options signature for the database connector.     $signature = md5(serialize($options));
    // If we already have a database connector instance for these options then just use that.     if (empty(self::$instances[$signature]))     {
        // Derive the class name from the driver.         $class = 'JDatabaseDriver' . ucfirst(strtolower($options['driver']));
        // If the class still doesn't exist we have nothing left to do but throw an exception. We did our best.         if (!class_exists($class))         {             throw new RuntimeException(sprintf('Unable to load Database Driver: %s', $options['driver']));         }
        // Create our new JDatabaseDriver connector based on the options given.         try         {             $instance = new $class($options);         }         catch (RuntimeException $e)         {             throw new RuntimeException(sprintf('Unable to connect to the Database: %s', $e->getMessage()));         }
        // Set the new connector to the global instances based on signature.         self::$instances[$signature] = $instance;     }
    return self::$instances[$signature]; }

JDatabaseDriver的getInstance()方法

 

如果我们总是将数据库连接传递给需要它的对象,那么依赖关系将是明确的。但现在它是隐藏的。大多数时候,我们在需要的地方产生数据库连接。这就是使用单例作为全局的一个例子。

您可以在 $config 中将数据库对象传递给模型,但如果您不这样做,则将实例化一个

public function __construct($config = array())
{
    // (...)
    // Set the model dbo
    if (array_key_exists('dbo', $config))
    {
        $this->_db = $config['dbo'];
    }
    else
    {
        $this->_db = JFactory::getDbo();
    }
    // (...)
}

JModelLegacy 构造函数的一部分

 

两年前,为新的 MVC 构建了一些基本类。您可以在 Joomla 框架中找到它们。当您查看 Joomla\Model\AbstracDatabaseModel 类时,您会看到其构造函数中的一个 $db 参数。这是一个 依赖注入的例子:将依赖(对数据库连接的依赖)注入到模型对象中

public function __construct(DatabaseDriver $db, Registry $state = null)
{
    $this->db = $db;
 
    parent::__construct($state);
}

Joomla\Model\AbstracDatabaseModel 的构造函数

这个新的 DatabaseModel 旨在在需要数据库连接的地方使用。不需要像现在在 Joomla CMS 模块中使用的带有静态方法的 Helper 类(另一个代码问题),因为如果数据库连接始终注入到模型中,我们就不再需要单例了。选择不同的驱动程序可以通过其他更合适的设计模式如 Builder、Bridge 或 Strategy 来完成。由于 Joomla 框架不需要向后兼容,因此可以从 Joomla\Database\DatabaseDriver 中移除 getInstance() 的单例实现。

一些参考资料

 

在 Joomla 社区杂志上发表的一些文章代表了作者对特定主题的个人观点或经验,可能并不与 Joomla 项目官方立场一致

0
在 Joomla! 中使用 Microdata 的实践...
 

评论

已经注册?请在这里登录
尚无评论。成为第一个发表评论的人

通过接受,您将访问由 https://magazine.joomla.net.cn/ 外部第三方提供的服务