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 依赖注入和松耦合的主要内容,如果未能解决你的问题,请参考以下文章