在基于PHP的面向对象项目中,如何组织和管理辅助对象,如数据库引擎,用户通知,错误处理等?
假设我有一个大的PHP CMS . CMS由各种类别组织 . 几个例子:
-
数据库对象
-
用户管理
-
用于创建/修改/删除项目的API
-
一个消息传递对象,用于向最终用户显示消息
-
一个将您带到正确页面的上下文处理程序
-
显示按钮的导航栏类
-
一个记录对象
-
可能,自定义错误处理
等等
我正在处理永恒的问题,如何最好地使这些对象可以访问需要它的系统的每个部分 .
很多年前,我的第一个应用程序是拥有一个包含这些类的初始化实例的$ application全局 .
global $application;
$application->messageHandler->addMessage("Item successfully inserted");
然后我切换到Singleton模式和工厂函数:
$mh =&factory("messageHandler");
$mh->addMessage("Item successfully inserted");
但我对此也不满意 . 单元测试和封装对我来说变得越来越重要,在我的理解中,全局/单例背后的逻辑破坏了OOP的基本思想 .
那么当然有可能给每个对象提供它需要的辅助对象的许多指针,可能是最干净,资源节省和测试友好的方式,但我对这长期可维护性有疑问 .
我研究过的大多数PHP框架都使用单例模式或访问初始化对象的函数 . 这两种方法都不错,但正如我所说的那样,我对它们都不满意 .
我想扩大我对这里存在的常见模式的看法 . 我正在寻找示例,其他想法和指向从 long-term , real-world 角度讨论这个问题的资源 .
此外,我有兴趣了解该问题的专业,利基或 plain weird 方法 .
8 回答
我会避免Flavius提出的Singleton方法 . 有许多理由可以避免这种方法 . 它违反了良好的OOP原则 . 谷歌测试博客有一些关于Singleton的好文章以及如何避免它:
http://googletesting.blogspot.com/2008/08/by-miko-hevery-so-you-join-new-project.html http://googletesting.blogspot.com/2008/05/tott-using-dependancy-injection-to.html http://googletesting.blogspot.com/2008/08/where-have-all-singletons-gone.html
替代品
http://java.sun.com/blueprints/corej2eepatterns/Patterns/ServiceLocator.html
http://en.wikipedia.org/wiki/Dependency_injection
和一个PHP的解释:
http://components.symfony-project.org/dependency-injection/trunk/book/01-Dependency-Injection
这是一篇关于这些替代品的好文章:
http://martinfowler.com/articles/injection.html
实现依赖注入(DI):
我相信你应该ask what is needed in the constructor for the object to function:
new YourObject($dependencyA, $dependencyB);
您可以手动提供所需的对象(依赖项)(
$application = new Application(new MessageHandler()
) . 但您也可以使用DI框架(维基百科页面提供links to PHP DI frameworks) .重要的是,您只传入实际使用的内容(调用操作),而不是传递给其他对象的内容,因为它们需要它 . 在这里's a recent post from '叔叔鲍勃'(罗伯特马丁)讨论manual DI vs using framework .
关于Flavius解决方案的更多想法 . 我不希望这篇文章成为反帖,但我认为重要的是要看到为什么依赖注入至少对我来说比全局变量更好 .
即使它不是'true' Singleton实现,我仍然认为Flavius弄错了 . Global state is bad . 请注意,此类解决方案也使用difficult to test static methods .
我知道很多人都这样做,批准并使用它 . 但阅读Misko Heverys的博客文章(a google testability expert),重新阅读并慢慢消化他所说的内容确实改变了我看待设计的方式 .
如果您希望能够测试应用程序,则需要采用不同的方法来设计应用程序 . 当你进行测试优先编程时,你会遇到这样的问题:'接下来我想在这段代码中实现日志记录;让我们首先编写一个记录基本消息的测试,然后进行测试,强制您编写并使用无法替换的全局 Logger .
我仍然是struggling,其中包含了我从该博客获得的所有信息,并且在我掌握了Misko Hevery所说的内容之后,我无法回到之前所做的事情(是的,全球州和Singletons(大S)): - )
这就是我这样做的方式 . 它按需创建对象:
这就是我这样做的方式,它尊重OOP原则,它比现在你做的更少代码,并且只有在代码第一次需要它时才创建对象 .
Note :我为什么认为它的使用是有效的,因为它不会忽视良好的OOP原则 . 当然,任何事情在世界上,这个"pattern"也不应该被滥用!
我已经看到它在许多PHP框架中使用,Zend Framework和其中的Yii . 你应该使用一个框架 . 我不打算告诉你哪一个 .
Addendum 对于那些担心TDD的人来说,你仍然可以通过依赖注入来弥补它 . 它可能看起来像这样:
有足够的改进空间 . 它只是一个PoC,用你的想象力 .
为什么这样?好吧,大多数情况下,应用程序不会进行单元测试,它实际上会运行,希望在 生产环境 环境中运行 . PHP的优势在于它的速度 . PHP不是,也永远不会像Java一样"clean OOP language" .
在一个应用程序中,最多只有一个Application类,每个Helper只有一个实例(根据上面的延迟加载) . 当然, singletons are bad, but then again, only if they don't adhere to the real world. 在我的例子中,他们这样做 .
刻板的“规则”,如“单身人士是坏人”是邪恶的根源,他们是懒惰的人不愿意为自己思考 .
是的,我知道,从技术上讲,PHP宣言很糟糕 . 然而,它是一种成功的语言,以其黑客的方式 .
附录
一种功能风格:
我喜欢依赖注入的概念:
Fabien Potencier写了一篇非常好的series of articles about Dependency Injection以及使用它们的必要性 . 他还提供了一个很好的小型依赖注入容器,名为Pimple,我非常想使用它(更多关于github的信息) .
如上所述,我没有't like the use of Singletons. A good summary on why Singletons aren' t好的设计可以找到here in Steve Yegge's blog .
最好的方法是为这些资源设置一种 container . 实现此容器的一些最常用方法:
Singleton
不推荐,因为它很难测试并暗示全局状态 . (Singletonitis)
注册表
消除单一炎症,我也不建议使用注册表,因为它也是一种单身人士 . (难以进行单元测试)
继承
可惜,PHP中没有多重继承,因此这限制了所有链 .
依赖注入
这是一种更好的方法,但是一个更大的主题 .
传统
最简单的方法是使用构造函数或setter注入(使用setter或类构造函数传递依赖项对象) .
框架
您可以滚动自己的依赖注入器,或者使用一些依赖注入框架,例如 . Yadif
应用程序资源
您可以初始化应用程序引导程序(充当容器)中的每个资源,并在访问引导程序对象的应用程序中的任何位置访问它们 .
这是Zend Framework 1.x中实现的方法
资源加载器
一种静态对象,仅在需要时加载(创建)所需的资源 . 这是一种非常聪明的方法 . 你可能会看到它在行动,例如实施Symfony's Dependency Injection component
注入特定图层
在应用程序的任何地方都不总是需要资源 . 有时你只需要它们,例如在控制器中(MV C ) . 然后你可以只在那里注入资源 .
常见的方法是使用docblock注释添加注入元数据 .
在这里看到我的方法:
How to use dependency injection in Zend Framework? - Stack Overflow
最后,我想在这里添加一个非常重要的注释 - 缓存 .
通常,尽管您选择了技术,但您应该考虑如何缓存资源 . 缓存将是资源本身 .
应用程序可能非常大,并且在每次请求时加载所有资源非常昂贵 . 有很多方法,包括appserver-in-php - Project Hosting on Google Code .
如果你想让全局的对象可用,registry pattern对你来说可能很有意思 . 如需灵感,请查看Zend Registry .
Registry vs. Singleton问题也是如此 .
PHP中的对象占用了大量内存,正如您可能从单元测试中看到的那样 . 因此,理想的是尽快销毁不需要的对象以节省其他进程的内存 . 考虑到这一点,我发现每个物体都适合两个模具中的一个 .
1)对象可能有许多有用的方法或需要多次调用,在这种情况下我实现单例/注册表:
2)对象只存在于调用它的方法/函数的生命周期中,在这种情况下,简单的创建有利于防止延迟对象引用保持对象活得太久了 .
存储临时对象 ANYWHERE 可能会导致内存泄漏,因为对它们的引用可能会忘记将对象保留在内存中以用于脚本的其余部分 .
我会去函数返回初始化对象:
在测试环境中,您可以定义它以返回模型 . 您甚至可以使用debug_backtrace()检测调用该函数的内部并返回不同的对象 . 你可以在里面注册谁想要得到什么对象来获得一些见解你的程序内部实际发生了什么 .
为什么不读精细手册?
http://php.net/manual/en/language.oop5.autoload.php