Is there a use-case for singletons with database access in PHP?
我通过PDO访问我的MySQL数据库。我正在设置对数据库的访问,我的第一次尝试是使用以下内容:
我想到的第一件事是
1 2 3 4 5 6 | $db = new PDO('mysql:host=127.0.0.1;dbname=toto', 'root', 'pwd'); function some_function() { global $db; $db->query('...'); } |
这被认为是一种不好的做法。经过一番搜索,我最终得到了单子模式,即
"applies to situations in which there needs to be a single instance of a class."
根据手册中的示例,我们应该这样做:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | class Database { private static $instance, $db; private function __construct(){} static function singleton() { if(!isset(self::$instance)) self::$instance = new __CLASS__; return self:$instance; } function get() { if(!isset(self::$db)) self::$db = new PDO('mysql:host=127.0.0.1;dbname=toto', 'user', 'pwd') return self::$db; } } function some_function() { $db = Database::singleton(); $db->get()->query('...'); } some_function(); |
当我能做到这一点时,为什么我需要那个相对较大的班级?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | class Database { private static $db; private function __construct(){} static function get() { if(!isset(self::$db)) self::$db = new PDO('mysql:host=127.0.0.1;dbname=toto', 'user', 'pwd'); return self::$db; } } function some_function() { Database::get()->query('...'); } some_function(); |
最后一个非常好用,我不需要再担心
如何创建一个较小的singleton类,或者是否有一个PHP中缺少的singleton用例?
在PHP中,单例语言很少(如果不说不使用的话)使用。
在对象位于共享内存中的语言中,可以使用单例来降低内存使用率。不是创建两个对象,而是引用全局共享应用程序内存中的现有实例。在PHP中没有这样的应用程序内存。在一个请求中创建的单例实例正是为该请求而存在的。在另一个同时完成的请求中创建的单例仍然是完全不同的实例。因此,单例的两个主要目的之一在这里不适用。
此外,许多概念上在应用程序中只能存在一次的对象不一定需要语言机制来实现这一点。如果您只需要一个实例,那么不要实例化另一个实例。只有当你可能没有其他的实例时,例如当你创建第二个实例时小猫死了,你才可能有一个单例的有效用例。
另一个目的是在同一个请求中拥有对实例的全局访问点。虽然这听起来可能是可取的,但实际上并非如此,因为它创建了与全局范围的耦合(就像任何全局和静态)。这使得单元测试更加困难,并且通常您的应用程序的可维护性更低。有一些方法可以缓解这种情况,但一般来说,如果您需要在许多类中具有相同的实例,请使用依赖注入。
在PHP中查看我的单件幻灯片—为什么它们不好,以及如何从应用程序中消除它们以获取更多信息。
即使是辛格尔顿模式的发明者之一埃里希·伽玛(Erich Gamma)也对这种模式表示怀疑:
"I'm in favor of dropping Singleton. Its use is almost always a design smell"
进一步阅读
- 如何在PHP中硬测试注册表模式或singleton?
- 将PHP数据库类用作单例有什么缺点?
- 使用php pdo的数据库抽象类设计
- 单件是一个好的微博网站的设计模式吗?
- 修改类以封装而不是继承
- 如何从其他类访问对象?
- 为什么单例在PHP中没有用处
- 清洁代码谈判-单子和全球国家
如果上述情况发生后,您仍然需要帮助决定:
好吧,当我第一次开始我的职业生涯的时候,我想了一会儿。以不同的方式实现它,并提出了两个选择不使用静态类的原因,但它们都是非常大的类。
一个是,你会经常发现一些你绝对确信永远不会有一个以上实例的东西,你最终会有一秒钟。您最终可能会得到第二个监视器、第二个数据库、第二个服务器——无论什么。
当发生这种情况时,如果使用了静态类,那么重构比使用单例更糟糕。单例模式本身就是一个不确定的模式,但它很容易转换为智能工厂模式——甚至可以转换为使用依赖注入,而不会有太多麻烦。例如,如果您的singleton是通过getInstance()获得的,那么您可以很容易地将其更改为getInstance(databaseName),并允许多个数据库——不需要更改其他代码。
第二个问题是测试(老实说,这与第一个问题相同)。有时您希望用模拟数据库替换您的数据库。实际上,这是数据库对象的第二个实例。使用静态类比使用单例要困难得多,您只需模拟getInstance()方法,而不是静态类中的每个方法(在某些语言中可能非常困难)。
归根结底,这取决于习惯——当人们说"全球人"不好的时候,他们有很好的理由这么说,但这可能并不总是显而易见的,除非你自己解决了问题。
你能做的最好的事情就是问(像你一样),然后做出选择,观察你的决定的后果。拥有解释代码随时间变化的演化的知识比一开始就正确地执行代码要重要得多。
谁需要PHP中的单例?好的。
注意,几乎所有对单件产品的反对意见都来自于技术观点——但它们的范围也非常有限。尤其是PHP。首先,我将列出使用单例的一些原因,然后我将分析对使用单例的异议。首先,需要他们的人:好的。
-编写大型框架/代码库(将在许多不同的环境中使用)的人员必须与以前存在的不同框架/代码库一起工作,必须执行客户/老板/管理层/单位领导提出的许多不同、变化甚至异想天开的请求。好的。
看,单例模式是自包含的。完成后,单例类对于包含它的任何代码都是严格的,它的行为与创建方法和变量的方式完全相同。在给定的请求中,它始终是相同的对象。因为它不能两次创建为两个不同的对象,所以您知道代码中的任何给定点上的singleton对象是什么——即使singleton被插入到两个、三个不同的、旧的、甚至是意大利面条的代码基中。因此,它使开发的目的更加简单——即使在该项目中有许多人在工作,当您看到在任何给定的代码库中的一个点中初始化一个单例时,您知道它是什么、它做什么、它如何做以及它所处的状态。如果它是传统类,那么您需要跟踪该对象最初是在哪里创建的,在代码中的这一点之前在其中调用了什么方法,以及它的特定状态。但是,将一个单例放到那里,如果在编码时将正确的调试和信息方法以及跟踪放到了单例中,您就可以确切地知道它是什么。因此,它使那些必须处理不同代码库的人更容易处理,有必要将以前用不同哲学处理过的代码集成起来,或者由与你没有联系的人处理。(也就是说,供应商项目公司,无论什么都没有,没有支持什么)。好的。
-需要使用第三方API、服务和网站的人员。好的。
如果您仔细观察,这与前面的情况没有太大的不同——第三方API、服务、网站就像外部的、独立的代码库,您无法控制它们。任何事情都可能发生。因此,使用单点会话/用户类,您可以管理来自第三方提供商(如OpenID、Facebook、Twitter等)的任何类型的会话/授权实现,您可以同时从同一个单点对象执行所有这些操作,无论您插入什么代码,都可以在任何已知状态下轻松访问该对象。去。您甚至可以在自己的网站/应用程序中为同一用户创建多个不同的、第三方API/服务的会话,并对它们进行任何您想做的操作。好的。
当然,所有这些都可以通过使用普通的类和对象来与传统的方法协调——这里的要点是,与在这种情况下使用传统的类/对象相比,singleton更整洁,因此更易于管理/测试。好的。
-需要快速发展的人好的。
单例的类似全局的行为使得用一个框架来构建任何类型的代码变得更容易,这个框架有一个单例集合可以构建,因为一旦您构建好了单例类,已经建立的、成熟的和设置好的方法将很容易在任何地方、任何时间、以一致的方式可用。你的课程需要一段时间才能成熟,但在那之后,它们是坚如磐石的、始终如一的、有用的。您可以在一个单独的实例中使用任意多个方法,尽管这可能会增加对象的内存占用,但它可以为快速开发节省更多的时间—您在某个应用程序的某个给定实例中没有使用的方法可以在另一个集成的实例中使用,并且您可以通过CH客户/老板/项目经理只需稍作修改就可以提出要求。好的。
你明白了。现在,让我们继续讨论反对单身的理由不圣洁的十字军对抗有用的东西:好的。
-最主要的反对意见是它使测试更加困难。好的。
实际上,它在某种程度上是可以做到的,即使它可以通过采取适当的预防措施并将调试例程编码到您的单例中轻松地得到缓解,同时认识到您将要调试单例。但是,你看,这与其他任何编码原理/方法/模式都没有太大的不同——只是,单例是相对新的,并不普遍,所以当前的测试方法最终与它们相当不兼容。但这在编程语言的任何方面都没有不同——不同的风格需要不同的方法。好的。
有一点,这种反对意见很简单,它忽略了这样一个事实:开发应用程序的原因不是为了"测试",并且测试不是进入应用程序开发的唯一阶段/过程。应用程序是为生产而开发的。正如我在"谁需要单例"一节中所解释的,单例可以大大降低必须使代码与许多不同的代码库/应用程序/第三方服务一起工作以及在其中工作的复杂性。在测试中可能会损失的时间是在开发和部署中获得的时间。这在第三方认证/应用程序/集成的时代尤其有用-Facebook、Twitter、OpenID等等,还有谁知道接下来会发生什么。好的。
虽然这是可以理解的——程序员的工作环境不同,这取决于他们的职业生涯。对于那些在相对较大的公司工作的人来说,他们的部门以一种舒适的方式来处理不同的、定义好的软件/应用程序,并且没有即将到来的预算削减/裁员的厄运,同时也不需要以一种廉价/快速/可靠的方式用大量不同的东西做很多事情,单身汉似乎不那么必要。一。甚至可能会对他们已经拥有的东西造成滋扰/阻碍。好的。
但是对于那些需要在"敏捷"开发的肮脏的壕沟中工作的人来说,必须从他们的客户/经理/项目中实现许多不同的请求(有时是不合理的),由于前面解释的原因,单例是一种节省的宽限期。好的。
-另一个反对意见是它的内存占用更大好的。
因为每个客户机的每个请求都有一个新的单例,所以这可能是对PHP的反对。如果单例构建和使用不当,那么如果应用程序在任何给定点为许多用户提供服务,那么应用程序的内存占用可能会更大。好的。
不过,这对于您在编写代码时可以采用的任何方法都是有效的。应该问的问题是,这些单例保存和处理的方法、数据是不必要的吗?因为,如果在应用程序获取的许多请求中都需要这些方法和数据,那么即使您不使用单例,这些方法和数据也将通过代码以某种形式或另一种形式出现在应用程序中。因此,当您将一个传统的类对象1/3初始化为代码处理,并将其3/4销毁为代码处理时,这一切都变成了一个问题,您将要节省多少内存。好的。
请看,当这样说的时候,问题就变得非常不相关了——不应该有不必要的方法,也不应该在代码中的对象中保存数据——不管您是否使用单例。因此,这种对单例的反对变得非常可笑,因为它假定在从您使用的类创建的对象中会有不必要的方法和数据。好的。
-一些无效的反对意见,如"使维护多个数据库连接变得不可能/更困难"好的。
我甚至不能开始理解这一点,当所有人都需要维护多个数据库连接、多个数据库选择、多个数据库查询时,一个给定的单例中的多个结果集只是在需要的时候将它们保存在单例中的变量/数组中。这可以像将它们保存在数组中一样简单,尽管您可以发明任何您想使用的方法来实现这一点。但是,让我们来研究最简单的情况,在给定的单例中使用变量和数组:好的。
假设下面是给定数据库singleton中的:好的。
$this->connections=array();(语法错误,我只是这样键入它来给出图片-变量的正确声明是public$connections=array();它的用法是$this->connections['connectionkey']自然地)好的。
您可以以这种方式在一个数组中的任何给定时间设置和保持多个连接。查询、结果集等等也是如此。好的。
$this->query(querystring,'queryname',$this->connections['particularConnection']);好的。
它只需使用选定的连接对选定的数据库进行查询,并将其存储在好的。
$ ->结果好的。
带有"queryname"键的数组。当然,您需要为此对查询方法进行编码——这是很简单的事情。好的。
这使您能够根据需要维护几乎无限数量(当然,只要资源限制允许)的不同数据库连接和结果集。它们对任何给定代码库中任何给定点的任何代码段都是可用的,这个单例类已经被实例化到该代码库中。好的。
当然,如果不需要的话,您自然需要释放结果集和连接——但这是不言而喻的,而且它并不特定于单例或任何其他编码方法/样式/概念。好的。
此时,您可以看到如何在同一个单例中维护到第三方应用程序或服务的多个连接/状态。没什么不同。好的。
长话短说,归根结底,单例模式只是另一种编程方法/风格/哲学,当它们以正确的方式在正确的位置使用时,它们与其他模式一样有用。这和任何事情都没有区别。好的。
你会注意到,在大多数抨击单身汉的文章中,你也会看到对"全球"的引用是"邪恶的"。好的。
让我们面对现实吧——任何使用不当、滥用、误用的东西都是邪恶的。这不限于任何语言、任何编码概念、任何方法。每当你看到有人发表像"x是邪恶的"这样的笼统言论,就从那篇文章中跑开。很有可能这是一个有限观点的产物——即使这个观点是多年经验的结果——通常是在一个特定的风格/方法下工作太多——典型的知识保守主义。好的。
无穷无尽的例子,从"全球都是邪恶的"到"iframes都是邪恶的"。大约10年前,甚至提议在任何给定的应用程序中使用iframe也是异端邪说。然后是Facebook,iframes无处不在,看看发生了什么-iframes不再那么邪恶了。好的。
仍然有一些人顽固地坚持他们是"邪恶的",有时也是出于正当的理由——但是,正如你所看到的,有一种需要,如果名字能满足需要,并能很好地工作,那么整个世界就会继续前进。好的。
程序员/编码员/软件工程师最重要的资产是一个自由、开放和灵活的头脑。好的。好啊。
很多人认为单子是反模式的,因为它们实际上只是美化了全局变量。在实践中,很少有场景需要类只有一个实例;通常情况下,只需要一个实例就足够了,在这种情况下,完全不需要将它实现为单个实例。
回答这个问题,你是对的,单身汉在这里被杀的太多了。一个简单的变量或函数就可以了。然而,一个更好(更健壮)的方法是使用依赖注入来完全消除对全局变量的需求。
在您的示例中,您处理的是一段看似不变的信息。在这个例子中,单例会被过度杀戮,仅仅在类中使用一个静态函数就可以了。
更多的想法:你可能正在经历一个为了模式而实现模式的案例,你的直觉告诉你"不,你不必",因为你阐述的原因。
但是:我们不知道你们项目的规模和范围。如果这是一个简单的代码,也许可以扔掉,这不太可能需要更改,那么是的,继续使用静态成员。但是,如果您认为您的项目可能需要扩展,或者需要为将来的维护编码做准备,那么,是的,您可能希望使用单例模式。
简单地考虑一下您的解决方案与PHP文档中的解决方案的区别。实际上,只有一个"小"区别:您的解决方案为getter的调用者提供了一个
那么我们得出什么结论呢?
- 在文档代码中,调用者得到一个
Database 实例。Database 类可能会公开(实际上,如果您要遇到所有这些问题,它应该公开)比它所包装的PDO 对象更丰富或更高级别的接口。 - 如果您将实现更改为返回另一个比
PDO 更丰富的类型,那么这两个实现是等效的。遵循手册的实施是没有好处的。
从实践的角度来看,单件是一种颇具争议的模式。这主要是因为:
- 它被过度使用了。新手程序员摸索singleton要比他们摸索其他模式容易得多。然后,他们继续将他们新发现的知识应用到任何地方,即使手头的问题不需要单件就能更好地解决(当你拿着锤子时,一切看起来都像钉子)。
- 根据编程语言的不同,以密封、无泄漏的方式实现一个单例可以证明是一项艰巨的任务(特别是如果我们有高级场景:单例依赖于另一个单例,单例可以被销毁和重新创建等)。试着在C++中搜索"决定性的"单体实现,我敢说(我拥有Andrei Alexandrescu开创性的现代C++设计,它记录了很多乱七八糟的东西)。
- 它在编写单例代码和编写代码以访问单例代码时都会增加额外的工作负载,您可以通过遵循一些对您的程序变量所做的一些自定约束来完成这些工作负载。
所以,最后的结论是:你的单身生活很好。在大多数情况下,不使用单件也很好。
首先,我想说的是,我对单例模式没有太多的用处。为什么要让一个对象贯穿整个应用程序?尤其是对于数据库,如果我想连接到另一个数据库服务器呢?每次我都要断开和重新连接…?总之…
在应用程序中使用globals有几个缺点(这是传统的singleton模式所做的):
- 单元测试困难
- 依赖注入问题
- 可以创建锁定问题(多线程应用程序)
使用静态类而不是单例实例也提供了一些相同的缺点,因为单例的最大问题是静态
不使用传统的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | class Single { static private $_instance = false; public function __construct() { if (self::$_instance) throw new RuntimeException('An instance of '.__CLASS__.' already exists'); self::$_instance = true; } private function __clone() { throw new RuntimeException('Cannot clone a singleton class'); } public function __destruct() { self::$_instance = false; } } $a = new Single; $b = new Single; // error $b = clone($a); // error unset($a); $b = new Single; // works |
这将在前面提到的点上有所帮助:单元测试和依赖注入;同时确保应用程序中存在类的单个实例。例如,您可以将生成的对象传递给您的模型(MVC模式),供它们使用。
编程时,没有"对"和"错";有"好的实践"和"坏的实践"。
单例通常被创建为一个类,稍后重用。它们的创建方式必须确保程序员在午夜醉酒编码时不会意外地实例化两个实例。
如果您有一个简单的小类,它不应该被实例化一次以上,那么您不需要使它成为单例。如果你这样做的话,它只是一个安全网。
拥有全局对象并不总是坏事。如果你知道你要在全球/任何地方/任何时候使用它,它可能是少数例外之一。然而,全球金融机构通常被视为"坏做法",正如
你的解释是正确的。单身汉有自己的位置,但使用过度。通常,访问静态成员函数就足够了(尤其是在不需要以任何方式控制构造时间的情况下)。更好的方法是,只需在名称空间中放置一些自由的函数和变量。
我一点也不明白。如果以这样的方式实现类,即将连接字符串作为构造函数的参数,并维护一个PDO对象列表(每个唯一的连接字符串一个),那么可能会有一些好处,但是在这个实例中实现singleton似乎是一个无意义的练习。
据我所见,你什么都没有遗漏。这个例子有很大的缺陷。如果singleton类有一些非静态的实例变量,这将有所不同。