7

我在这里考虑几种不同的方法,非常感谢一些输入!我正在考虑以下两种选择。那里有两件事我有疑问。

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

  2. 在第二个示例中,类的依赖项通过构造函数注入,然后通过类的属性在其中维护。然后在调用方法(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();
    }

}
4

2 回答 2

8

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

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

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

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

给定一个构造函数

public function __construct(); 

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

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

于 2013-05-31T04:15:29.980 回答
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)

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

$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;
    }
}
于 2013-05-31T11:43:08.357 回答