81

在 PHP 中使用单例而不是全局进行数据库连接有什么好处?我觉得使用单例而不是全局会使代码变得不必要地复杂。

全球代码

$conn = new PDO(...);

function getSomething()
{
    global $conn;
    .
    .
    .
}

单例代码

class DB_Instance
{
    private static $db;

    public static function getDBO()
    {
        if (!self::$db)
            self::$db = new PDO(...);

        return self::$db;
    }
}

function getSomething()
{
    $conn = DB_Instance::getDBO();
    .
    .
    .
}

如果除了全局或单例之外还有更好的初始化数据库连接的方法,请提及并描述它相对于全局或单例的优势。

4

9 回答 9

105

我知道这是旧的,但 Dr8k 的答案几乎就在那里。

当您考虑编写一段代码时,假设它会发生变化。这并不意味着你假设它会在未来的某个时候对其进行更改,而是会进行某种形式的更改。

把它作为一个目标来减轻未来做出改变的痛苦:一个全局是危险的,因为它很难在一个地方进行管理。如果我想在将来让数据库连接上下文感知怎么办?如果我希望它在每 5 次使用时关闭并重新打开怎么办。如果我决定为了扩展我的应用程序我想使用一个包含 10 个连接的池怎么办?还是可配置的连接数?

单例工厂为您提供了这种灵活性。我以很少的额外复杂性进行设置,并且获得的不仅仅是访问相同的连接;我获得了稍后以简单的方式更改该连接传递给我的方式的能力。

请注意,我说的是单例工厂,而不是简单的单例。单例和全局之间几乎没有什么区别,真的。正因为如此,没有理由建立一个单例连接:当你可以创建一个常规的全局变量时,为什么还要花时间设置它呢?

工厂为您提供的是为什么要获得联系,以及一个单独的位置来决定您将获得哪些联系(或联系)。

例子

class ConnectionFactory
{
    private static $factory;
    private $db;

    public static function getFactory()
    {
        if (!self::$factory)
            self::$factory = new ConnectionFactory(...);
        return self::$factory;
    }

    public function getConnection() {
        if (!$this->db)
            $this->db = new PDO(...);
        return $this->db;
    }
}

function getSomething()
{
    $conn = ConnectionFactory::getFactory()->getConnection();
    .
    .
    .
}

然后,在 6 个月内,当您的应用程序非常有名并被挖走并被 slashdotted 时,您决定需要多个连接,您所要做的就是在 getConnection() 方法中实现一些池化。或者,如果您决定想要一个实现 SQL 日志记录的包装器,您可以传递一个 PDO 子类。或者,如果您决定在每次调用时都需要一个新连接,您可以这样做。它是灵活的,而不是刚性的。

16 行代码,包括大括号,这将为您节省数小时和数小时的重构时间。

请注意,我不考虑这种“功能蔓延”,因为我在第一轮中没有做任何功能实现。这是“Future Creep”的边界线,但在某些时候,“为今天的明天编码”总是一件坏事的想法对我来说并不适用。

于 2008-10-20T19:37:20.160 回答
16

我不确定我是否可以回答您的具体问题,但我想建议如果对于基于 Web 的系统,全局/单例连接对象可能不是最好的主意。DBMS 通常被设计为以有效的方式管理大量的唯一连接。如果您使用的是全局连接对象,那么您正在做几件事:

  1. 强制您的页面按顺序执行所有数据库连接并终止异步页面加载的任何尝试。

  2. 可能在数据库元素上保持打开锁的时间超过必要时间,从而降低整体数据库性能。

  3. 最大化您的数据库可以支持的同时连接总数,并阻止新用户访问资源。

我相信还有其他潜在的后果。请记住,此方法将尝试为访问该站点的每个用户维持数据库连接。如果您只有一两个用户,那不是问题。如果这是一个公共网站并且您想要流量,那么可扩展性将成为一个问题。

[编辑]

在更大规模的情况下,每次访问数据时都创建新连接可能很糟糕。但是,答案不是创建一个全局连接并将其重用于所有内容。答案是连接池。

使用连接池,可以维护许多不同的连接。当应用程序需要连接时,将从池中检索第一个可用连接,然后在其工作完成后将其返回到池中。如果请求连接并且没有可用的连接,将发生以下两种情况之一:a) 如果未达到允许的最大连接数,则打开一个新连接,或 b) 应用程序被迫等待连接变为可用.

注意:在 .Net 语言中,连接池默认由 ADO.Net 对象处理(连接字符串设置所有必需的信息)。

感谢克拉德对此发表评论。

于 2008-09-25T01:05:47.047 回答
7

创建单例方法是为了确保任何类只有一个实例。但是,由于人们将其用作快捷全球化的一种方式,因此它被称为懒惰和/或糟糕的编程。

因此,我会忽略 global 和 Singleton,因为两者都不是真正的 OOP。

您正在寻找的是依赖注入

您可以在http://components.symfony-project.org/dependency-injection/trunk/book/01-Dependency-Injection上查看易于阅读的与依赖注入相关的基于 PHP 的信息(带有示例)

于 2011-12-13T00:35:10.247 回答
3

这两种模式实现了相同的净效果,为您的数据库调用提供了一个单一的访问点。

在具体实现方面,单例有一个小优势,即在您的至少一个其他方法请求它之前不启动数据库连接。实际上,在我编写的大多数应用程序中,这并没有太大的区别,但是如果您有一些根本不进行任何数据库调用的页面/执行路径,这是一个潜在的优势,因为这些页面不会曾经请求连接到数据库。

另一个小的区别是全局实现可能会无意中践踏应用程序中的其他变量名。您不太可能意外地声明另一个全局 $db 引用,尽管您可能会意外地覆盖它(例如,当您打算编写 if($db == null) 时,您编写了 if($db = null)。单例对象可以防止这种情况。

于 2008-09-25T02:44:19.760 回答
2

如果您不打算使用持久连接,并且在某些情况下不这样做,我发现单例在概念上比面向对象设计中的全局更可口。

在真正的 OO 架构中,单例比每次都为对象创建一个新实例更有效。

于 2008-09-25T01:03:55.867 回答
2

在给定的示例中,我认为没有理由使用单例。根据经验,如果我唯一关心的是允许对象的单个实例,如果语言允许,我更喜欢使用全局变量

于 2008-09-25T01:06:13.053 回答
1

一般来说,我会使用单例进行数据库连接...您不想每次需要与数据库交互时都创建新连接...这可能会损害网络的性能和带宽...为什么要创建一个新的,当有可用的时候......只要我的 2 美分......

温迪

于 2008-09-25T01:14:39.803 回答
0

这很简单。永远不要使用全局 OR Singleton。

于 2008-09-25T00:59:56.470 回答
0

作为建议,单例全局都是有效的,并且可以在同一个系统、项目、插件、产品等中加入......在我的例子中,我为网络(插件)制作数字产品。

我在主类中只使用单例,并且我原则上使用它。我几乎不使用它,因为我知道主类不会再次实例化它

<?php // file0.php

final class Main_Class
{
    private static $instance;
    private $time;

    private final function __construct()
    {
        $this->time = 0;
    }
    public final static function getInstance() : self
    {
        if (self::$instance instanceof self) {
            return self::$instance;
        }

        return self::$instance = new self();
    }
    public final function __clone()
    {
        throw new LogicException("Cloning timer is prohibited");
    }
    public final function __sleep()
    {
        throw new LogicException("Serializing timer is prohibited");
    }
    public final function __wakeup()
    {
        throw new LogicException("UnSerializing timer is prohibited");
    }
}

几乎所有次要课程的全球使用,例如:

<?php // file1.php
global $YUZO;
$YUZO = new YUZO; // YUZO is name class

而在运行时,我可以使用Global在同一个实例中调用它们的方法和属性,因为我不需要我的主要产品类的另一个实例。

<?php // file2.php
global $YUZO;
$YUZO->method1()->run();
$YUZO->method2( 'parameter' )->html()->print();

我得到的全局是使用相同的实例来使产品工作,因为我不需要同一类的实例的工厂,通常实例工厂用于大型系统或非常罕见的目的。

In conclusion:,如果你已经很好地理解了反模式Singleton并且理解了Global,你必须使用这两个选项之一或混合它们,但如果我建议不要滥用,因为有很多程序员非常例外并且忠实于编程 OOP,将它用于在执行时间内经常使用的主要和次要类。(它为你节省了大量的 CPU)。

于 2018-05-27T18:36:25.827 回答