PHP依赖注入和松散耦合

时间:2013-05-31 03:54:05

标签: php design-patterns interface dependency-injection

我在这里思考一些不同的方法,非常感谢一些输入!我正在考虑以下两个选择。有两件事情我会有问题。

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

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

  3. 确实没有必要将类中的任何内容存储为属性。我可以重新安排一切使用这种技术而不会有太多麻烦,我想我更喜欢它。这样我也可以将所有工作从构造函数中移出,然后通过方法简单地访问所有内容。我在这里走在正确的轨道上吗?

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

2 个答案:

答案 0 :(得分:7)

最好将依赖项注入构造函数。

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

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

我可以立即告诉这个组件需要做什么工作。

给出像

这样的构造函数
public function __construct(); 

无法确定组件需要运行的功能。它创建了与您的每个路由器,您的请求和渲染器的特定实现的强大耦合,在您深入了解您的类的内容之前,这些都不会显而易见。

总之,第一种方法有详细记录,可扩展和可测试。 第二种方法是不透明的,高度耦合的,不易测试。

答案 1 :(得分:2)

虽然Orangepill提出了一个很好的观点,但我认为我也会参与其中。我倾向于用一个清晰​​的构造函数来定义我的构造函数,但是我不希望期望在创建实例时传递所需的对象。
有时,您创建一个实例,从数据库或某种Http请求中检索 数据。在您的情况下,第一个示例需要传递三个依赖项,但是谁会说您将始终需要所有三个依赖项?

输入Lazy-Loading。下面的代码示例非常冗长,但它(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)

如果我想运行一些测试,我仍然可以在构造我的实例时注入依赖项,或者我可以依赖于test-config对象我可以使用setDbsetCurl方法:

$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;
    }
}