为什么单例在PHP环境中如此糟糕


Why is singleton so bad in a PHP environment?

可能的重复项:
谁需要单身人士?

我想知道,在 PHP 脚本中使用单例有什么缺点。我经常使用它们,有时我无法理解对开发人员的批评。一些例子:

我有一个请求类:

清理 POST、GET、COOKIE 输入数据并使用它代替全局数组 - 严格和全局。喜欢

$request = Request::getInstance();
$firstname = $request->post('firstname', $additionalFilters);

每个请求始终只有一个请求。为什么在这种情况下使用单例是一个坏主意?

同样为 _SESSION 美元:

我有一个会话类(单例(,它确实表示 $_SESSION 数组,因为只有一个会话,我全局使用它。

数据库

$mysql  = DB::getInstance('mysql', 'dbname'); //pseudo
$sqlite = DB::getInstance('sqlite', 'dbname'); //pseudo

对于每种类型的数据库,我只想要一个对象,而不是更多。在我看来,否则就有混乱的风险。

唯一行

此外,我经常使用类来表示/使用 db 表的唯一行。

$article = Article::getInstance($id);
$navigation = Navigation::getInstance($id);

我只看到这样做的好处。我永远不想要表示唯一行的第二个对象。为什么单例在这里是一个坏主意?

事实上,我的大多数(几乎所有(类都没有公共构造函数,但总是像getInstance($id(或create((这样的静态方法,所以类本身处理可能的实例(这并不意味着它们都是单例定义(

所以我的问题是:是否有任何我还没有意识到的缺点。单例怀疑者在建议反对单例时会考虑哪种具体情况。

编辑:

现在,你得到了一个包裹在 _POST 美元的单例,但是如果你 没有 $_POST,但想改用文件进行输入?在那 在这种情况下,如果你有一个抽象的输入类会更方便, 并实例化 POSTInput 以通过发布的数据管理输入。

好的,有效的优势。我没有意识到这一点。尤其是关于请求类的一点。

我仍然怀疑这种做法是否如此。假设我有一个执行具体请求(如留言簿组件(的"功能"类。在该类中,我想获取一个发送的参数。所以我得到了请求的单例实例

$req = Request::getInstance(); 
$message = $req->post('message');

这样,只有我的功能对象关心 Request 类。

当我使用非单例方法时,我需要以某种方式附加一个类/函数来管理每个请求都获取有效的请求对象。这样,我的 Functional 类就不需要知道该管理类,但在我看来仍然存在依赖/问题:每次我创建 Functional 对象的 instace 时,我都可能忘记设置请求对象。

当然,我可以在创建功能时定义一个非可选参数。但这会导致参数在某些时候完全矫枉过正。要不?

单例(和静态类,它们在很大程度上适用相同的故事(本身还不错,但它们引入了您可能不想要的依赖项。

现在,你得到了一个包裹 $_POST 的单例,但是如果你没有 $_POST,但想使用文件进行输入怎么办?在这种情况下,如果您有一个抽象输入类,并实例化一个 POSTInput 以通过发布的数据管理输入,则会更方便。

如果要从文件中获取输入,甚至(无论出于何种原因(想要模拟(或重播(基于数据库表输入的多个请求,您仍然可以执行此操作,而无需更改任何代码,但实例化类的部分除外。

这同样适用于其他类。你不希望你的整个应用程序与这个MySQL单例对话。如果您需要连接到两个 MySQL 数据库怎么办?如果你需要切换到WhatEverSQL怎么办?..为这些类型的类创建摘要,并重写它们以实现特定技术。

我认为单例不应该像在基于请求的架构(如PHP或 ASP.NET(或任何你想要的架构中那样受到负面报道。 从本质上讲,在常规程序中,该单例的生命周期可以与程序运行的月数或数年一样多:

int main()
{
    while(dont_exit)
    {
        // do stuff
        Singleton& mySingleton = Singleton::getInstance();
        // use it
    }
    return 0;
}

除了只是一个全局变量之外,很难用在单元测试中可能有用的单例替换该单例。 在可能数百个源文件中,可能依赖于它的代码量将该单例的使用与整个程序紧密耦合。

也就是说,对于基于请求的方案(例如 PHP 页面或 ASP.NET 页面(,所有可调用代码实际上都包装在函数调用中。 同样,他们混淆了全局变量(但在请求的上下文中(,以防止多次创建。

但是,我仍然主张反对使用它们。 为什么? 因为即使在单个请求的上下文中,所有内容也依赖于该实例并紧密耦合。 如果要使用不同的请求对象测试不同的方案,会发生什么情况? 假设您使用包含进行编码,现在必须修改该调用的每个实例。 如果您已经传递了对预构造的 Request 类的引用,您现在可以做一些很酷的事情,例如提供类的模拟单元测试版本,只需更改传递给其他函数的内容即可。 您还分离了使用此通用请求对象的所有内容。