在 PHP 项目中,存在哪些模式来存储、访问和组织帮助程序对象?[关闭]

In a PHP project, what patterns exist to store, access and organize helper objects? [closed]

提问人: 提问时间:11/28/2009 最后编辑:19 revs, 2 users 100%Pekka 웃 更新时间:12/28/2011 访问量:13270

问:

已锁定。这个问题及其答案被锁定,因为这个问题偏离了主题,但具有历史意义。它目前不接受新的答案或交互。

如何在基于 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 框架都使用单例模式或访问初始化对象的函数。这两种方法都很好,但正如我所说,我对两者都不满意。

我想拓宽我的视野,了解这里存在哪些常见的模式。我正在寻找从长期的、现实世界的角度讨论这个问题的例子其他想法和指向资源的指针。

此外,我很想听听这个问题的专业、小众或奇怪的方法。

设计模式 OOP PHP

评论

1赞 philfreo 1/25/2010
我刚刚问了一个非常相似的问题,也有赏金。您可能会欣赏那里的一些答案:stackoverflow.com/questions/1967548/......
3赞 ryeguy 1/29/2010
只是抬头,通过引用返回一个新对象 - like 是没有意义的,不会产生任何性能优势。此外,这在 5.3 中已弃用。$mh=&factory("messageHandler");

答:

16赞 10 revs, 2 users 81%Flavius #1
class Application {
    protected static $_singletonFoo=NULL;

    public static function foo() {
        if(NULL === self::$_singletonFoo) {
            self::$_singletonFoo = new Foo;
        }
        return self::$_singletonFoo;
    }

}

这就是我的做法。它按需创建对象:

Application::foo()->bar();

这就是我做事的方式,它尊重 OOP 原则,它比你现在做的代码要少,而且对象只有在代码第一次需要它时才会创建。

注意:我所介绍的甚至不是真正的单例模式。通过将构造函数 (Foo::__constructor()) 定义为私有,单例将只允许自身的一个实例。它只是所有“应用程序”实例可用的“全局”变量。这就是为什么我认为它的使用是有效的,因为它不会忽视良好的 OOP 原则。当然,就像世界上的任何东西一样,这种“模式”也不应该被过度使用!

我已经看到它被用于许多PHP框架,其中包括Zend Framework和Yii。你应该使用一个框架。我不会告诉你是哪一个。

补遗对于那些担心 TDD 的人来说,你仍然可以编造一些连接来注入依赖关系。它可能看起来像这样:

class Application {
        protected static $_singletonFoo=NULL;
        protected static $_helperName = 'Foo';

        public static function setDefaultHelperName($helperName='Foo') {
                if(is_string($helperName)) {
                        self::$_helperName = $helperName;
                }
                elseif(is_object($helperName)) {
                        self::$_singletonFoo = $helperName;
                }
                else {
                        return FALSE;
                }
                return TRUE;
        }
        public static function foo() {
                if(NULL === self::$_singletonFoo) {
                        self::$_singletonFoo = new self::$_helperName;
                }
                return self::$_singletonFoo;
        }
}

有足够的改进空间。这只是一个PoC,发挥你的想象力。

为什么要这样做?大多数情况下,应用程序不会进行单元测试,而是实际运行,希望在生产环境中运行。PHP的优势在于它的速度。PHP 不是,也永远不会像 Java 那样成为“干净的 OOP 语言”。

在应用程序中,只有一个 Application 类,并且每个帮助程序最多只有一个实例(如上所述的延迟加载)。当然,单身人士是坏事,但话又说回来,前提是他们不遵守现实世界。在我的例子中,他们确实如此。

刻板印象的“规则”,如“单身人士是坏人”,是邪恶的根源,它们是懒惰的人,不愿意自己思考。

是的,我知道,从技术上讲,PHP 宣言很糟糕。然而,它是一种成功的语言,以它黑客的方式。

补遗

一种功能风格:

function app($class) {
    static $refs = array();

    //> Dependency injection in case of unit test
    if (is_object($class)) {
        $refs[get_class($class)] = $class;
        $class = get_class($class);
    }

    if (!isset($refs[$class]))
        $refs[$class] = new $class();

    return $refs[$class];
}

//> usage: app('Logger')->doWhatever();

评论

2赞 koen 11/29/2009
我对这个答案投了反对票,因为我相信建议使用单例模式来处理这个问题违背了坚实的 OOP 原则。
1赞 Flavius 11/29/2009
@koen:一般来说,你说的是真的,但据我所知,他指的是应用程序的帮助程序,而在一个应用程序中只有一个......呃,应用。
0赞 Flavius 1/22/2010
注意:我所介绍的甚至不是真正的单例模式。通过将构造函数定义为私有构造函数,单例将只允许类的一个实例。它只是所有“应用程序”实例可用的“全局”变量。这就是为什么我认为它的有效性不会忽视良好的 OOP 原则。当然,就像世界上的任何东西一样,这种“模式”也不应该被过度使用。
0赞 just somebody 1/24/2010
-1 也来自我。它可能只是 Singleton DP 的一半,但它是丑陋的:“提供对它的全局访问”。
2赞 Daniel Von Fange 1/25/2010
这确实使他现有的方法更加简洁。
68赞 8 revs, 2 users 79%koen #2

我会避免 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

选择

  1. 服务提供商

    http://java.sun.com/blueprints/corej2eepatterns/Patterns/ServiceLocator.html

  2. 依赖注入

    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):

  • 我相信你应该问构造函数中需要什么才能使对象发挥作用new YourObject($dependencyA, $dependencyB);

  • 您可以手动提供所需的对象(依赖项)()。但你也可以使用 DI 框架(维基百科页面提供了 PHP DI 框架的链接)。$application = new Application(new MessageHandler()

    重要的是,你只传入你实际使用的东西(调用一个动作),而不是你简单地传递给其他对象,因为它们需要它。这是“鲍勃叔叔”(Robert Martin)最近发表的一篇帖子,讨论了手动DI与使用框架

关于弗拉维乌斯的解决方案的更多想法。我不希望这篇文章是反帖子,但我认为重要的是要了解为什么依赖注入,至少对我来说,比全局更好。

尽管它不是“真正的”单例实现,但我仍然认为 Flavius 弄错了。全局状态很糟糕。请注意,此类解决方案还使用难以测试的静态方法

我知道很多人都这样做,批准它并使用它。但是阅读 Misko Heverys 的博客文章(谷歌可测试性专家),重读它并慢慢消化他所说的内容确实改变了我对设计的看法。

如果希望能够测试应用程序,则需要采用不同的方法来设计应用程序。当你进行测试优先编程时,你会遇到这样的困难:“接下来,我想在这段代码中实现日志记录;让我们先编写一个记录基本消息的测试,然后提出一个测试,强制您编写和使用无法替换的全局记录器。

我仍然在为我从该博客中获得的所有信息而苦苦挣扎,而且实施起来并不总是那么容易,而且我有很多问题。但是在我理解了 Misko Hevery 在说什么之后,我无法回到我之前所做的事情(是的,全局状态和 Singletons(大 S)):-)

评论

0赞 Anurag 1/23/2010
+1 表示 DI。虽然我没有像我想的那样使用它,但它对我使用它的任何少量都非常有帮助。
1赞 Alix Axel 1/23/2010
@koen:想举一个PHP中DI / SP实现的PHP示例吗?也许使用您建议的替代模式实现@Flavius代码?
0赞 Thomas Müller 1/23/2010
在我的回答中添加了指向 DI 实现和容器的链接。
0赞 JasonDavis 1/24/2010
我现在正在阅读所有这些内容,但我还没有全部阅读,我想问一下,依赖注入框架基本上是注册表吗?
0赞 Thomas Müller 1/24/2010
不,不是真的。但是依赖注入容器也可以用作注册表。只需阅读我在答案中发布的链接即可。DI的成分得到了非常实际的解释。
15赞 4 revsThomas #3

我喜欢依赖注入的概念:

“依赖注入是指组件通过其构造函数、方法或直接进入字段来赋予其依赖关系。(摘自Pico Container网站)"

Fabien Potencier 写了一系列关于依赖注入和使用它们的必要性的非常好的文章。他还提供了一个名为 Pimple 的漂亮而小的依赖注入容器,我非常喜欢使用它(更多信息在 github 上)。

如上所述,我不喜欢使用单例。关于为什么单例不是好设计的一个很好的总结可以在 Steve Yegge 的博客中找到

评论

0赞 Juraj Blahunka 1/23/2010
我喜欢PHP中通过Closures实现,读起来非常有趣
0赞 Thomas Müller 1/23/2010
我也是,他还有一些关于关闭他网站的其他需求:fabien.potencier.org/article/17/......
2赞 Juraj Blahunka 1/23/2010
让我们希望,主流网站将很快迁移到 PHP 5.3,因为看到功能齐全的 PHP 5.3 服务器仍然不常见
0赞 Thomas Müller 1/24/2010
当越来越多的项目需要 PHP 5.3 时,他们将不得不这样做,比如 Zend Framework 2.0 将 framework.zend.com/wiki/display/ZFDEV2/......
1赞 Juraj Blahunka 1/25/2010
依赖注入也被接受,回答了以下问题:stackoverflow.com/questions/1580210/...,并举了一个非常好的例子decupling from GOD object
6赞 2 revsFelix Kling #4

如果要使对象全局可用,则注册表模式可能对您感兴趣。要获得灵感,请查看 Zend Registry

注册表与单例问题也是如此。

评论

0赞 Thomas Müller 1/24/2010
如果你不想使用 Zend Framework,这里有一个很好的 PHP5 注册表模式实现: phpbar.de/w/Registry
0赞 1/29/2010
我更喜欢类型化的注册表模式,比如 Registry::GetDatabase(“master”);注册表::GetSession($user->SessionKey());注册表::GetConfig(“本地”);[...]并为每种类型定义一个接口。这样一来,你就可以确保你不会意外地覆盖用于不同事物的键(即你可能有一个“主数据库”和一个“主配置”。通过使用接口,可以确保仅使用有效的对象。当然,这也可以通过使用多个注册表类来实现,但恕我直言,单个注册表类更简单、更易于使用,但仍然具有优势。
0赞 Gnuffo1 10/27/2010
或者当然是 PHP 中内置的那个 - _GLOBALS 美元
4赞 Xeoncross #5

PHP 中的对象会占用大量内存,正如您可能在单元测试中看到的那样。因此,理想的做法是尽快销毁不需要的对象,为其他进程节省内存。考虑到这一点,我发现每个对象都适合两种模具之一。

1)该对象可能有许多有用的方法,或者需要多次调用,在这种情况下,我实现了一个单例/注册表:

$object = load::singleton('classname');
//or
$object = classname::instance(); // which sets self::$instance = $this

2) 对象仅在调用它的方法/函数的生命周期内存在,在这种情况下,简单的创建有利于防止挥之不去的对象引用使对象保持活动时间过长。

$object = new Class();

将临时对象存储在任何位置可能会导致内存泄漏,因为在脚本的其余部分将对象保留在内存中时,可能会忘记对它们的引用。

-1赞 gcb #6

为什么不阅读精美的手册呢?

http://php.net/manual/en/language.oop5.autoload.php

评论

0赞 Pekka 1/30/2010
谢谢 gcb,但类的加载不是我关心的问题,我的问题更具架构性。
0赞 jjnguy 2/6/2012
虽然这在理论上可以回答问题,但最好在此处包含答案的基本部分,并提供链接以供参考。
3赞 Kamil Szot #7

我会选择返回初始化对象的函数:

A('Users')->getCurrentUser();

在测试环境中,您可以将其定义为返回模型。您甚至可以使用 debug_backtrace() 检测内部谁调用了该函数并返回不同的对象。你可以在里面注册谁想要得到什么对象,以获得一些洞察力,你的程序中实际发生了什么。

9赞 2 revstakeshin #8

最好的方法是为这些资源提供某种容器。实现此容器的一些最常见方法:

单身 人士

不建议这样做,因为它很难测试,并且意味着全局状态。(单张力炎)

注册表

消除单调炎,错误我也不推荐注册表,因为它也是一种单例。(难以单元测试)

遗产

可惜的是,PHP 中没有多重继承,所以这限制了所有链。

依赖注入

这是一个更好的方法,但是一个更大的话题。

传统的

最简单的方法是使用构造函数或 setter 注入(使用 setter 或在类构造函数中传递依赖对象)。

框架

你可以滚动自己的依赖注入器,或者使用一些依赖注入框架,例如。亚迪夫

应用程序资源

您可以在应用程序引导程序(充当容器)中初始化每个资源,并在访问引导程序对象的应用程序中的任何位置访问它们。

这是在 Zend Framework 1.x 中实现的方法

资源加载程序

一种静态对象,仅在需要时加载(创建)所需的资源。这是一个非常聪明的方法。您可能会看到它的实际效果,例如实现 Symfony 的依赖注入组件

注入到特定层

应用程序中的任何位置并不总是需要这些资源。有时您只需要它们,例如在控制器 (MV C) 中。然后,您可以仅在此处注入资源。

常见的方法是使用 docblock 注释来添加注入元数据。

请在此处查看我的方法:

如何在 Zend Framework 中使用依赖注入?- 堆栈溢出

最后,我想在这里添加一个关于非常重要的事情的注释 - 缓存。
通常,尽管您选择了哪种技术,但您都应该考虑如何缓存资源。缓存将是资源本身。

应用程序可能非常大,并且根据每个请求加载所有资源的成本非常高。有很多方法,包括这个 appserver-in-php - Google Code 上的项目托管