PHP 依赖注入和松耦合

Posted

技术标签:

【中文标题】PHP 依赖注入和松耦合【英文标题】:PHP Dependency Injection and Loose Coupling 【发布时间】:2013-05-26 19:33:47 【问题描述】:

我在这里考虑了几种不同的方法,非常感谢您的意见!我正在考虑以下两种选择。有两件事我有疑问。

    是首选将依赖项注入主“容器”类的构造函数,还是在容器类中创建新实例?

    在第二个示例中,类的依赖项通过构造函数注入,然后通过类的属性在内部进行维护。然后在调用方法(route()、render())时,从内部调用依赖项。我从这种方法开始,但现在我更喜欢第一个示例的内容。我认为第一个示例更可取,但是在第二个示例中使用 DI 方法有什么好处吗?

确实没有必要在类中存储任何东西作为属性。我可能可以毫不费力地重新安排所有内容以使用该技术,而且我认为我更喜欢它。这样我也可以将所有工作从构造函数中移出,稍后通过方法简单地访问所有内容。我在正确的轨道上吗?

class App

    private $config;
    private $router;
    private $renderer; 

    public function __construct(IConfig $config, IRouter $router, IRenderer $renderer)
    
        $this->config = $config;
        $this->router = $router;
        $this->renderer = $renderer;

        $this->run();           
    

    public function run()
    
        $data = $this->router->route(new Request, $config->routes);         
        $this->renderer->render($data);
       




class App

    private $config;
    private $router;
    private $renderer; 

    public function __construct()
    
        $this->config = new Config;

        $this->run();               
    

    public function run()
    
        $this->router = new Router(new Request, $config->routes);
        $this->router->route();

        $this->renderer = new Renderer($this->router->getData());
        $this->renderer->render();
    


【问题讨论】:

【参考方案1】:

最好在构造函数中注入依赖。

在构造函数中创建实例会在两个类之间建立紧密耦合。使用带有明确签名的构造函数,例如

 public function __construct(IConfig $config, IRouter $router, IRenderer $renderer)

我可以立即告诉这个组件需要什么来完成它的工作。

给定一个构造函数

public function __construct(); 

不知道该组件需要什么功能。它与您的每个路由器、您的请求和您的渲染器的特定实现建立了强大的耦合,在您深入研究类的核心之前,这些都不明显。

总之,第一种方法有据可查、可扩展和可测试。 第二种方法不透明、高度耦合且不易测试。

【讨论】:

【参考方案2】:

虽然 Orangepill 提出了一个很好的观点,但我想我也会加入。我也倾向于使用明确的构造函数来定义我的构造函数,但我不期望在创建实例时传递所需的对象。 有时,您会创建一个从数据库或某种 Http 请求中检索数据的实例。在您的情况下,第一个示例需要传递三个依赖项,但谁能说您总是需要所有三个依赖项?

进入延迟加载。下面的代码示例很长,但是(IMO)非常值得研究。如果我使用服务,我不想加载所有依赖项,除非我确定我会使用它们。这就是为什么我定义了构造函数,以便我可以通过以下任一方式创建实例:

$foo = new MyService($configObj);
$bar = new MyService($configObj, null, $dbObj);//don't load curl (yet)
$baz = new MyService($configObj, $curlObj);//don't load db (yet)

如果我想运行一些测试,我仍然可以在构建我的实例时注入依赖项,或者我可以依赖一个测试配置对象或者我可以使用setDb 和@987654324 @ 方法也是如此:

$foo->setCurl($testCurl);

坚持第一种构造实例的方式,我可以肯定地说,如果我只调用getViaCurl 方法,则永远不会加载Db 类。 getViaDb 方法稍微复杂一些(getDb 方法也是如此)。我不建议您使用这样的方法,但这只是为了向您展示这种方法的灵活性可以。我可以将一组参数传递给getViaDb 方法,该方法可以包含自定义连接。我还可以传递一个布尔值来控制我对该连接的操作(仅将它用于这个调用,或者将连接分配给MyService 实例。

我希望这不是太不清楚,但我很累,所以我不太擅长解释这些东西 ATM。 无论如何,这是代码......它应该非常自我解释。

class MyService

    private $curl = null;
    private $db = null;
    private $conf = null;
    public function __construct(Config $configObj, Curl $curlObj = null, Db $dbObj = null)
    
        $this->conf = $configObj;//you'll see why I do need this in a minute
        $this->curl = $curlObj;//might be null
        $this->db = $dbObj;
    

    public function getViaCurl(Something $useful)
    
        $curl = $this->getCurl();//<-- this is where the magic happens
        return $curl->request($useful);
    

    public function getViaDb(array $params)
    
        if (isset($params['custom']))
        
            $db = $this->getDb($params['custom'], $params['switch']);
        
        else
        //default
            $db = $this->getDb();
        
        return $db->query($params['request']);
    

    public function getCurl()
    //return current Curl, or load default if none set
        if ($this->curl === null)
        //fallback to default from $this->conf
            $this->curl = new Curl($this->conf->getSection('CurlConf'));
        
        return $this->curl;
    

    public function setCurl(Curl $curlObj)
    //inject after instance is created here
         if ($this->curl instanceof Curl)
         //close current connection
             $this->curl->close();
         
         $this->curl = $curlObj;
    

    public function setDb(Db $dbObj)
    
        if ($this->db instanceof Db)
        //commit & close
            $this->db->commit();
            $this->db->close();
        
        $this->db = $dbObj;
    

    //more elaborate, even:
    public function getDb(Db $custom = null, $switch = false)
    
        if ($custom && !!$swith === true)
        
            $this->setDb($custom);
            return $this->db;
        
        if ($custom)
        //use custom Db, only this one time
            return $custom;
        
        if ($this->db === null)
        
            $this->db = new Db($this->conf->getSection('Db'));
        
        return $this->db;
    

【讨论】:

我知道这是一个老回复,但是,实现 cUrl 和 DB 的接口不是这样吗?有什么like this?

以上是关于PHP 依赖注入和松耦合的主要内容,如果未能解决你的问题,请参考以下文章

“依赖注入”,“控制反转”是指啥?

PHP中的服务容器与依赖注入的思想

PHP中的服务容器与依赖注入的思想

PHP中的服务容器与依赖注入的思想

YII容器类依赖注入

控制反转,依赖注入