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

2022-08-30 07:20:58

如何在基于 PHP 的面向对象项目中组织和管理帮助程序对象,如数据库引擎、用户通知、错误处理等?

假设我有一个大型PHP CMS。CMS分为不同的类别。举几个例子:

  • 数据库对象
  • 用户管理
  • 用于创建/修改/删除项目的 API
  • 用于向最终用户显示消息的消息传递对象
  • 将您带到正确页面的上下文处理程序
  • 显示按钮的导航栏类
  • 日志记录对象
  • 可能的自定义错误处理

等。

我正在处理一个永恒的问题,即如何最好地使这些对象可供需要它的系统的每个部分访问。

许多年前,我的第一个应用是拥有一个包含这些类的初始化实例的$application全局。

global $application;
$application->messageHandler->addMessage("Item successfully inserted");

然后我切换到单例模式和工厂函数:

$mh =&factory("messageHandler");
$mh->addMessage("Item successfully inserted");

但我对此也不满意。单元测试和封装对我来说变得越来越重要,在我的理解中,全局/单例背后的逻辑破坏了OOP的基本思想。

然后,当然有可能为每个对象提供一些指向它需要的帮助器对象的指针,这可能是最干净,最节省资源和测试友好的方式,但从长远来看,我对此的可维护性持怀疑态度。

我研究过的大多数PHP框架都使用单例模式或访问初始化对象的函数。两种方法都很好,但正如我所说,我对两者都不满意。

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

另外,我有兴趣听到有关该问题的专业,利基或简单怪异的方法。


答案 1

我会避免弗拉维乌斯提出的辛格尔顿方法。避免这种方法的原因有很多。它违反了良好的OOP原则。谷歌测试博客有一些关于辛格尔顿以及如何避免它的好文章:

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

    重要的是,您只传递您实际使用的内容(调用操作),而不是简单地传递给其他对象的内容,因为它们需要它。这是“鲍勃叔叔”(罗伯特·马丁)最近的一篇文章,讨论了手动DI与使用框架

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

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

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

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

我仍然在努力处理从该博客中获得的所有信息,并且并不总是易于实施,并且我有很多问题。但是,在我掌握了Misko Hevery所说的话之后,我无法回到我之前所做的事情(是的,全球状态和单例(大S)):-)


答案 2
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();

推荐