依赖注入始终是正确的答案吗?

时间:2019-07-20 11:39:25

标签: php dependency-injection service-locator

我目前正在考虑很多有关依赖注入的问题,这是赞成和反对的。一般而言,似乎已经达成共识,在大多数情况下依赖注入是正确的选择。我看到了它的方便之处以及它如何使代码更具可读性。因此,我正在尝试创建具有尽可能多的依赖项注入的类,将关注点分离为多个对象。在我70%的日常工作中,这很好。它有效,我看到了好处。

但是剩下的30%让我有些挣扎。我对依赖注入本身的概念没有任何疑问,但是我认为PHP确实具有一些“特殊属性”,这使我怀疑依赖注入是正确的选择。

  1. 要点似乎是使用DI代替PRO的要点,比如说服务定位符是您拥有“编译时错误”而不是“运行时错误”。对于Java或C这样的语言,我明白了,但是PHP中没有诸如“编译时错误”之类的东西不会自动导致“运行时错误”。至少我从未遇到过。

  2. 在一次启动的程序中,将其源代码加载到内存中,并在执行之前一直停留在内存中,我明白了为什么要使用DI。这是有道理的,您不必(通常)担心应用程序需要多少时间才能达到运行状态,并且您(通常)应该有足够的内存将所有代码保存在那里。因此,加载所有依赖项并将它们保留在内存中似乎很好。但是,PHP脚本在Apache,NGINX或其他上下文中使用时,每次用户访问时都会启动。除此之外,我们希望我们的应用程序以尽可能少的资源尽可能快地运行,以充分利用服务器硬件。关键是,如果每次加载整个库,即使我只是访问一部分代码,这似乎也是很浪费的……(下面有我的意思的例子)

  3. 如上所述,我尝试尽可能多地使用DI。但是剩下的30%我仍然使用service-locator模式来处理,因为我a。)在特定条件下只需要一个依赖项,或者b。)我访问一个可以被全局函数替代的服务/帮助器类。 (请参见下面的示例)

在这方面,我读了很多有关a。)助手类是邪恶的b。)当您在类中仅使用一次依赖项时,应将其拆分为一个单独的类(老实说,这是我的观点。不太了解,因为使用DI时无论如何都要创建类,为什么还要将其与普通类分开?)。

我创建了一些伪代码来展示30%的情况(在撰写本文时)(在PHP中)(在PHP中)服务定位符比DI更明智。

class RecordHandler {
    // Use a trait to enable access to the getInstanceOf() method,
    // which is a link to the global instance of the service container singleton
    use ContainerAwareTrait;

    /**
     * @var EnvService
     */
    protected $envService;

    /**
     * RecordHandler constructor.
     *
     * @param EnvService $envService
     */
    public function __construct(EnvService $envService) {
        $this->envService = $envService;
    }

    // ... Other methods...

    public function filterRecords(array $records): array {
        // Using the normal DI object...
        if($this->envService->isStaging()){
            // Do something different ...
            return [];
        }

        foreach($records as $recordId => $record){
            // This can happen in 10% of the times...
            // I don't know here if the database is already connected. 
            // The factory registered in the container will initialize the connection when the object is created...
            if(empty($record)){
                $record = $this->getInstanceOf(DbService::class)->query("SELECT * FROM `stored_record_table WHERE id = ?", [$recordId]);
            }

            // This can happen, but it does not happen in 50% of the cases...
            if(!empty($record["xmlField"])){
                $record["xmlField"] = $this->getInstanceOf(XmlService::class)->handleXml($record["xmlField"]);
            }

            // Creating a class instance based on the record's value...
            if(!empty($record["classField"]) && class_exists($record["classField"]) && 
                in_array(HandlerInterface::class, class_implements($record["classField"]))) {
                $i = $this->getInstanceOf($record["classField"]);
                $i->handle($record);
            }
        }
    }

请注意,EnvService和DbService在已使用的服务容器内被标记为“ Singleton”。这意味着一旦创建容器,容器将一次又一次返回相同的实例。

我真的很想知道是否有更好的解决方案,因为我围绕多个解决方案进行了测试,这样做可以节省30-50%的执行时间,并根据处理的记录类型而有所波动。

我也意识到诸如隐藏依赖之类的缺点。为了避免该问题,我尝试创建“上下文对象”,这些对象基本上是硬编码的DI容器,但似乎也不是这种选择。

最后,我尝试使用“延迟加载”来避免上述问题,但是例如PHP-DI在其文档中明确指出,您不应为超过3-4个类创建延迟加载代理,这似乎没有帮助。

1 个答案:

答案 0 :(得分:0)

没有模式总是答案;在无法推迟决策时,做出明智,务实的架构决策很重要。

您正确地指出,在PHP的情况下,由于必须在每个请求上实例化整个对象图,因此DI可能会导致性能下降。

但是,依赖注入显然与性能无关。它是一种实现控制反转的方法,最终实现了模块化设计,关注点分离,可重用性和可测试性。这些好处大大超过了“编译时”错误捕获功能。

在软件项目的早期阶段,很难预测它是否足够简单(占您的30%)来放弃所有这些好处,并寻求一种互连的设计而不是模块化的设计,运动部件紧密耦合在一起,无论是名称还是性能。