问题 为什么人们在他们的PHP框架中使用单例


好吧,我正在努力理解为什么需要单身人士。

让我们举一个真实的例子:我有一个CMS的框架
我需要一个记录一些信息的类(让我们坚持使用PHP)。

例:

class Logger{
   private $logs = array();

   public function add($log) {
      $this->logs[]=$log;
   }      
}

现在当然是这个 帮手 对于CMS的页面请求的输入生命周期,对象必须是唯一的。
为了解决这个问题,我们将它设为单例(声明私有构造函数等)

但为什么像这样的阶级并不是完全静止的呢?这将解决单例模式的需要(这被认为是不好的实践)示例:

class Logger {
    private static $logs = array();

    public static function add($log) {
        self::$logs[]=$log;
    }
}

通过使这个帮助器完全静态,当我们需要在我们的应用程序中的某处添加日志时,我们只需要静态地调用它: Logger::add('log 1'); 对单身人士的呼叫如: Logger::getInstance()->add('log 1');

希望有人能让我轻松理解为什么 在静态类上使用单例 用PHP。

编辑

这很漂亮 感谢@James,有关单身人士与静态课程的好评,感兴趣的是谁。 (注意它没有解决我的问题)


6074
2018-05-15 23:57


起源

我想念你的问题 - 实际上我完全赞同你 - 如果我可以使用静态类,我从不使用单例模式。 - Justin
我知道这不是一个简单的问题 - dynamic
在你的第二种方法中,如果你实际上声明函数是静态的,那将会有所帮助。使用 public static function add($log)。这样,每个人都会知道它必须静态调用,而不是必须首先查看函数...当您想要开始单元测试并模拟一些调用时,静态调用/类似这些可能是一个真正的痛苦。这就是依赖注入将派上用场的地方:p。 - wimvds


答案:


很多原因。

静态方法基本上是可以从任何范围调用的全局函数,这有助于难以跟踪错误。你也可以根本不使用课程。

由于您不能使用__construct方法,因此可能必须在某处放置init静态方法。现在,他们的代码中的人不确定先前是否已调用init方法。他们又叫了吗?他们是否必须在代码库中搜索此调用?如果init在某处,但随后被删除或中断怎么办?您的代码中的许多位置现在依赖于调用init方法的位置。

众所周知,静态方法很难通过许多单元测试框架进行单元测试。

还有很多原因,但很难将它们列出来。

如果您使用DI,则不需要单身人士。

旁注。 DI允许您的类不相互依赖,而是依赖于接口。由于他们的关系没有巩固,因此以后更容易更改您的应用程序,并且一个课程中断不会破坏这两个类。

在某些情况下,单个状态类是可行的,例如,如果您的方法都不依赖于其他方法(基本上没有一个方法会改变类的状态)。


7
2018-05-16 00:13



它给出了人们不使用静态类的一些原因,因此他们使用单例的原因。 - dqhendricks


我使用单身,所以我可以告诉你为什么我这样做而不是静态功能。

单例的定义特征是它是一个只有一个实例的类。很容易看到“只是一个实例”子句而忘记看“它是一个类”子句。毕竟,它是一个普通的类对象,具有所带来的所有优点。主要是,它有自己的状态,它可以有私有函数(方法)。静态函数必须以更有限或更笨拙的方式执行这两种操作。

也就是说,两者相辅相成:可以利用静态函数在同一个类上返回单例。这就是我在最常用的单例中做的事情:数据库处理程序。

现在,许多程序员都被教导说“单身人士很糟糕,不能吗?”但忽视骑手,事情往往只是坏事 什么时候过度使用。就像一位高手雕刻师一样,经验丰富的程序员可以使用大量工具,而且许多工具都不会得到很多用处。我的数据库处理程序非常适合作为单例,但它是 只要 一个我经常使用的。对于日志记录类,我通常使用静态方法。


5
2018-05-16 00:36





单身人士允许您覆盖行为。例如,只有当Logger类知道如何时,Logger :: add('1')才能登录到不同的设备。 Logger :: getLogger() - > add('1')可以执行不同的操作,具体取决于Logger getLogger()返回的子类型。 当然,您可以在记录器类中执行所有操作,但通常最终会在静态类中实现单例。


2
2018-05-16 00:19





如果你有一个打开文件的静态方法,写出并关闭它,你最终可能会尝试同时打开同一个文件的两个调用,因为静态方法不能保证有一个实例。

但是,如果您使用单例,则所有调用都使用相同的文件处理程序,因此您始终只能一次写入此文件。

您可能最终想要排队写请求,如果有多个,如果您不希望它们失败,或者您必须以其他方式同步,但所有调用将使用相同的实例。

更新:

在PHP中,这可能有助于静态与单例的比较。

http://moisadoru.wordpress.com/2010/03/02/static-call-versus-singleton-call-in-php/


1
2018-05-16 00:03



我不认为你的例子可以很好地引用我对框架中的辅助对象的意思。请记住,我推荐使用PHP,其中静态对象不在请求之间共享 - dynamic
@ yes123 - 请求是否共享无关紧要,因为网页非常不确定,因此您可能同时多次调用同一个静态函数,如果您需要处理正在使用每个调用应具有独占访问权限的资源。 - James Black
在PHP中没有魔法世界同步,你可以在方法附近添加以实现独占访问:)所以单身和静态对象都会遇到同样的问题你的观点 - dynamic
还要感谢您的链接。那篇文章指的是我的意思,但是请给我一个问题的答案:) XD - dynamic
@ yes123 - 您可以通过处理错误,在再次尝试之前等待一段时间,或者检测到已经有写入内容并再次等待来阻止多个调用进入静态日志写入功能。不理想,但可能,这就是为什么写入队列和使用单例可能更好。 - James Black


正如leblonk所提到的,你不能覆盖静态类,这使得单元测试非常困难。使用单例,您可以实例化“模拟”对象而不是实际类。无需更改代码。

静态类可能存在名称空间冲突。您不能加载2个相同名称的静态类,但您可以加载2个不同版本的单例并以相同名称实例化它们。当我需要测试新版本的类时,我已经这样做了。我实例化了该类的不同版本,但不需要更改引用该类的代码。

我经常混合单身和静态。例如,我使用一个数据库类,确保每个主服务器(静态)和从服务器(单例)只有1个连接。 db类的每个实例都可以连接到不同的slave,如果请求连接到同一个slave,则返回singleton对象。主连接是在每个从属单例内实例化的静态对象,因此在所有db实例化对象中只存在1个主连接。


1
2018-05-16 01:46