谁需要在PHP中使用单例模式?
注意到几乎所有对单例模式的反对都来自技术角度 - 但它们在范围上也非常有限,特别是对于PHP。首先,我将列出一些使用单例模式的原因,然后分析使用单例模式的反对意见。首先,需要它们的人:
- 编写大型框架/代码库的人,这些框架/代码库将在许多不同的环境中使用,必须使用之前存在的、不同的框架/代码库,并需要实现来自客户/老板/管理层/单位领导的许多不同的、变化的、甚至是异想天开的请求。
你看,单例模式是自包含的。当完成后,一个单例类在任何引用它的代码中都是不可改变的,其行为完全符合你创建方法和变量的方式。而且在给定的请求中始终是相同的对象。由于不能创建两个不同的对象,因此你可以在任何代码中的任何给定点上了解单例对象 - 即使将单例插入到两个、三个不同的旧蜘蛛网代码库中。因此,在开发目的上更容易 - 即使有许多人在该项目中工作,当你看到单例在任何代码库中的某个点被初始化时,你知道它是什么,它做什么,如何做以及它所处的状态。如果它是传统的类,你需要跟踪对象最初创建的位置,在代码中调用了哪些方法以及它特定的状态。但是,把一个单例放在那里,如果你在编写代码时加入了适当的调试和信息方法和跟踪,你就知道它是什么。因此,它使那些必须与不同代码库一起工作的人更容易,其需要将先前用不同哲学或由你没有联系的人完成的代码集成进去。(也就是说,供应商-项目-公司或其他已不再提供支持的情况)。
- 需要使用第三方APIs、服务和网站的人。
如果你仔细看,这与早期情况并没有太大区别 - 第三方API、服务、网站就像是你无法控制的外部独立代码库。任何事情都可能发生。因此,通过单例会话/用户类,您可以管理来自第三方提供商(如OpenID、Facebook、Twitter等)的任何类型的会话/授权实现,并且您可以同时从同一个单例对象中进行所有操作 - 这个对象很容易访问,在任何时候都处于已知状态,无论您将其插入到哪个代码中。您甚至可以为您自己的网站/应用程序中的同一用户创建多个会话以连接到多个不同的第三方API/服务,并对它们执行任何您想要的操作。
当然,所有这些也可以使用普通类和对象的传统方法来完成 - 这里的问题是,单例更加整洁、简洁,因此在这种情况下与传统的类/对象使用相比更易于管理/测试。
- 需要进行快速开发的人
单例的全局行为使得使用具有一组单例构建的框架构建任何类型的代码变得更加容易,因为一旦你很好地构建了你的单例类,已经建立、成熟和设置的方法将随时随地以一致的方式轻松可用。虽然需要一些时间来成熟你的类,但之后它们就会非常稳定、一致和有用。你可以在一个单例中拥有许多方法来做任何你想做的事情,尽管这可能会增加对象的内存占用,但它带来的节省时间远远超过了这个代价 - 你在一个应用程序实例中没有使用的方法可以在另一个集成的实例中使用,并且你只需要进行一些修改就可以添加客户/老板/项目经理要求的新功能。
你明白了吧。现在让我们继续讨论对单例的反对意见和反对某些有用东西的不可思议的追求:
- 最主要的反对意见是它使得测试更加困难。
实际上,单例模式在某种程度上确实存在问题,即使通过采取适当的预防措施和将调试程序编码到单例中来缓解这些问题,同时也要认识到您将需要调试单例。但是请注意,这与任何其他编码哲学/方法/模式并没有太大区别 - 只是单例相对较新且不普及,因此当前的测试方法与它们相比较不兼容。但这在编程语言的任何方面都没有什么不同 - 不同的风格需要不同的方法。
这个反对意见的一个问题在于,它忽略了应用程序开发的原因不是为了“测试”,而测试也不是应用程序开发中唯一的阶段/过程。应用程序是为生产使用而开发的。正如我在“谁需要单例”部分所解释的那样,单例可以从必须使代码在许多不同的代码库/应用程序/第三方服务中工作的复杂性中削减很多。在测试中可能会浪费的时间,在开发和部署中获得的时间更多。这在第三方身份验证/应用程序/集成的时代尤其有用 - Facebook、Twitter、OpenID、更多的服务以及未来的服务。
尽管可以理解 - 程序员的工作环境因其职业而异。对于那些在相对大型公司中工作,有明确定义的部门管理不同的软件/应用程序,并且在舒适的方式下没有预算削减/裁员的迫在眉睫和伴随而来的需要以便宜/快速/可靠的方式完成许多不同的任务,单例可能看起来并不是必要的。甚至可能会妨碍他们已经拥有的东西。
但是对于那些需要在“敏捷”开发的肮脏战壕中工作,需要实现客户/经理/项目提出的许多不同请求(有时是不合理的),由于前面所述的原因,单例是一种拯救之道。
- 另一个反对意见是其内存占用较高
因为每个客户端的每个请求都将存在一个新的单例,这可能是PHP的一个反对意见。如果单例构造和使用不当,则应用程序的内存占用量可能会更高,如果在任何给定点为许多用户提供服务,则情况更加如此。
然而,这适用于你编写程序时采取的任何方法。应该问的问题是,这些单例模式持有和处理的方法、数据是否是不必要的?如果它们在应用程序的多个请求中是必需的,那么即使您不使用单例模式,在代码中,这些方法和数据也会以某种形式存在于您的应用程序中。因此,这就变成了一个问题,当您在代码处理过程中初始化传统的类对象1/3并在3/4处销毁时,您将节省多少内存。
如此说来,这个问题就变得非常无关紧要 - 在您的代码中,无论您使用单例模式与否,都不应该存在不必要的方法和数据 - 这是不言自明的。因此,对单例模式的反对变得真正滑稽可笑,因为它假定您使用的类所创建的对象中将存在不必要的方法和数据。
- 一些无效的反对意见,例如“使维护多个数据库连接不可能/更难”
我甚至无法理解这个反对意见,因为只需要将多个数据库连接、多个数据库选择、多个数据库查询、多个结果集保持在单例中的变量/数组中,只要它们需要就可以了。这可以简单地通过将它们保存在数组中来实现,尽管您可以发明任何您想要使用的方法来实现它。但让我们来看看最简单的情况,在给定的单例中使用变量和数组:
想象下面的内容在一个给定的数据库单例中:
$this->connections = array(); (错误的语法,我只是这样打字来给你一个画面 - 变量的正确声明方式是public $connections = array();,它的用法是$this->connections['connectionkey']自然)
您可以以这种方式设置和保持多个连接。同样适用于查询、结果集等。
$this->query(QUERYSTRING,'queryname',$this->connections['particulrconnection']);
这只是使用所选连接对所选数据库进行查询,并将其存储在您的
$this->results
数组中的键名为“queryname”。当然,您需要编写查询方法来实现它 - 这很容易。
这使您能够维护几乎无限数量的(当然,只要资源限制允许)不同的数据库连接和结果集,尽可能多地使用它们。并且它们对于任何代码片段都是可用的,在任何给定代码库中被实例化的单例类中的任何给定点。
当然,您自然需要在不需要时释放结果集和连接 - 但这是不言而喻的,并且它与单例或任何其他编码方法/风格/概念无关。
此时,您可以看到如何在同一个单例中维护对第三方应用程序或服务的多个连接/状态。没那么不同。
长话短说,最终,单例模式只是另一种编程方法/风格/哲学,当它们在正确的位置以正确的方式使用时,它们就像任何其他有用的东西一样。这与其他任何事物没有什么不同。
您会注意到,在大多数批评单例的文章中,您还会看到对“全局变量”被视为“邪恶”的引用。
让我们面对现实吧——任何没有得到适当使用、被滥用和误用的东西都是邪恶的。这不限于任何语言、编码概念或方法。每当你看到有人发布类似“X是邪恶的”这样的笼统声明时,就要远离该文章。很有可能这是一种狭隘的观点产生的结果,即使这个观点是特定领域多年经验的结果,也通常是某种给定风格/方法下工作过度的结果——典型的知识保守主义。
可以举无数例子来说明这一点,从“全局变量是邪恶的”到“iframe是邪恶的”。大约十年前,甚至在任何一个应用程序中建议使用iframe都是异端邪说。然后Facebook出现了,到处都是iframe,结果怎么样呢?iframe不再那么邪恶了。
仍然有人执拗地坚称它们是“邪恶的”,有时也有充分的理由,但正如你所看到的,有需求,iframe能满足这种需求并且运作良好,因此整个世界就这样继续前进。
程序员/编码人员/软件工程师的最重要资产是一种自由、开放和灵活的思维方式。
function some_function($db = Database::get()){$db::query('...');}
- Alexander Behling