thp*_*hpl 122
答案变得比我想要的更长.我提供了一些背景信息.如果您正在寻找短期解释,请阅读IoC-Container的第一段和粗体段落.
依赖注入是一种设计模式,它遵循名称所述的内容.它将对象注入到构造函数或其他对象的方法中,因此一个对象依赖于一个或多个其他对象.
<?php
class DatabaseWriter {
protected $db;
public function __construct(DatabaseAdapter $db)
{
$this->db = $db;
}
public function write()
{
$this->db->query('...');
}
}
Run Code Online (Sandbox Code Playgroud)
您可以看到我们要求类构造函数DatabaseAdapter传递实例.由于我们在构造函数中执行此操作,因此在没有它的情况下无法实例化该类的对象:我们正在注入依赖项.现在,我们知道DatabaseAdapter在课堂上总是存在,我们可以轻松地依赖它.
该write()方法只是调用适配器上的方法,因为我们肯定知道它存在,因为我们使用了DI.
使用DI而不是滥用静态类,上帝对象和其他类似的东西的巨大优势是,您可以轻松地追踪依赖项的来源.
另一个巨大的优势是,您可以轻松地交换依赖项.如果要使用依赖项的其他实现,只需将其传递给构造函数.您不再需要搜索硬编码实例以便能够交换它们.
暂且不谈通过使用依赖注入这一事实,您可以轻松地对类进行单元测试,因为您可以模拟依赖项,这对于硬编码依赖项来说几乎是不可能的.
上面解释的依赖注入类型称为构造函数注入.这只是意味着依赖项作为参数传递给类构造函数.然后将依赖关系存储为属性,从而在类的所有方法中可用.这里的一大优点是,如果不传递依赖项,则该类的对象不可能存在.
此类型使用专用方法注入依赖项.而不是使用构造函数.使用Setter Injection的优点是,您可以在创建对象后添加依赖项.它通常用于可选的依赖项.Setter Injection也非常适合对构造函数进行整理,并且仅在需要它们的方法中使用依赖项.
<?php
class RegisterUserService {
protected $logger;
public function setLogger( Logger $logger )
{
$this->logger = $logger;
}
public function registerUser()
{
// Do stuff to register the user
if($this->logger)
$this->logger->log("User has been registered");
}
}
$service = new RegisterUserService;
$service->registerUser(); // Nothing is Logged
$service->setLogger(new ConcreteLogger);
$service->registerUser(); // Now we log
Run Code Online (Sandbox Code Playgroud)
可以在没有任何依赖性的情况下实例化对象.有一种方法可以注入依赖项(setLogger()),可以选择性地调用它.现在由方法实现来决定是否使用依赖(如果没有设置).
值得指出的是对Setter Injection持谨慎态度.调用方法或访问尚未注入的依赖项上的属性将导致令人讨厌Fatal error: Call to a member function XXX() on a non-object.因此,每次访问依赖项时,必须先对其进行空值检查.更简洁的方法是使用Null对象模式并将依赖项移动到构造函数中(作为可选参数,如果没有传递,则在类内创建null对象)
接口注入的想法基本上是注入依赖关系的方法在接口中定义.需要依赖的类必须实现接口.由此确保可以将所需的依赖性适当地注入到从属对象中.这是之前解释过的Setter Injection的一种更严格的形式.
<?php
interface Database {
public function query();
}
interface InjectDatabaseAccess {
// The user of this interface MUST provide
// a concrete of Database through this method
public function injectDatabase( Database $db );
}
class MySQL implements Database {
public function query($args)
{
// Execute Query
}
}
class DbDoer implements InjectDatabaseAccess {
protected $db;
public function injectDatabase( Database $db )
{
$this->db = $db;
}
public function doSomethingInDb($args)
{
$this->db->query();
}
}
$user = new DbDoer();
$user->injectDatabase( new MySQL );
$user->doSomethingInDb($stuff);
Run Code Online (Sandbox Code Playgroud)
接口注入的意义是有争议的.我个人从未使用过它.我更喜欢构造函数注入.这使我能够完成完全相同的任务,而无需喷射器侧的额外接口.
我们也可以依赖于抽象,而不是依赖于具体的实例.
<?php
class DatabaseWriter {
protected $db;
public function __construct(DatabaseAdapterInterface $db)
{
$this->db = $db;
}
public function write()
{
$this->db->query('...');
}
}
Run Code Online (Sandbox Code Playgroud)
现在我们颠倒了控制.我们现在可以注入任何消耗类型提示接口的实例,而不是依赖于具体实例.接口注意后面的具体实例实现了我们将要使用的所有方法,这样我们仍然可以依赖它们在依赖类中.
确保你得到那个,因为它是IoC-Container的核心概念.
在最短的时间里,我可以想到我会像这样描述IoC容器:
IoC-Container是一个组件,它知道如何创建实例并知道它们的所有底层依赖项以及如何解决它们.
如果我们采用上面的例子,想象一下它DatabaseAdapter本身就有它自己的依赖.
class ConcreteDatabaseAdapter implements DatabaseAdapterInterface{
protected $driver;
public function __construct(DatabaseDriverInterface $driver)
{
$this->driver = $driver;
}
}
Run Code Online (Sandbox Code Playgroud)
因此,为了能够使用,DatabaseAdapter您需要将DatabaseDriverInterface抽象实例作为依赖项传递.
但你的DatabaseWriter班级不知道,也不应该.本DatabaseWriter不应该在乎如何DatabaseAdapter工作,应该只有一个关怀DatabaseAdapter传递,而不是需要如何被创建.这就是IoC-Container派上用场的地方.
App::bind('DatabaseWriter', function(){
return new DatabaseWriter(
new ConcreteDatabaseAdapter(new ConcreteDatabaseDriver)
);
});
Run Code Online (Sandbox Code Playgroud)
正如我已经说过的,它DatabaseWriter本身并不了解它的依赖关系的依赖性.但是IoC-Container知道它们的全部内容并且知道在哪里找到它们.因此,当最终DatabaseWriter要实例化类时,首先会询问IoC-Container 如何实例化它.这就是IoC-Container的功能.
简单说一句(如果你是设计模式).它是一个依赖注入容器(我已经在上面解释过)和服务定位器的组合.
mhn*_*dev 10
考虑你有一个类似下面的Car类:
class Car
{
protected $wheels;
protected $engine;
public function __construct()
{
$this->engine = new Engine('BMW Engine');
$this->wheels = array(
new Wheel('some Wheel Brand'),
new Wheel('some Wheel Brand'),
new Wheel('some Wheel Brand'),
new Wheel('some Wheel Brand')
);
}
public function go()
{
//do fun stuff here
}
}
Run Code Online (Sandbox Code Playgroud)
现在考虑重复使用你的代码,例如你想要另一个适用于丰田公司的汽车类.你应该在上面编写完全代码,只需进行一些编辑,如下所示:
class AnotherCar
{
protected $wheels;
protected $engine;
public function __construct()
{
$this->engine = new Engine('Toyota Engine');
$this->wheels = array(new Wheel('some Wheel Brand'),
new Wheel('some Wheel Brand'),
new Wheel('some Wheel Brand'),
new Wheel('some Wheel Brand'));
}
public function go()
{
//do fun stuff here
}
}
Run Code Online (Sandbox Code Playgroud)
如果不使用DI(依赖注入)或IoC(控制反转),您的代码将会增长并增长.
让我们谈谈上面的代码问题:1-它不可重复使用.2-它不关心性能(你的Ram中的代码量,...)3-你的代码增长了,一段时间后你自己也不会理解它....
所以让我们使用DI并解决问题
class Car
{
protected $wheels;
protected $engine;
public function __construct($myWheels , $myEngine)
{
$this->engine = $myEngine;
$this->wheels = $myWheels;
}
public function go()
{
//do fun stuff here
}
}
Run Code Online (Sandbox Code Playgroud)
现在很容易就可以使用以下Car类创建每个汽车对象:
$BMW = new Car (new Engine('BMW' , array(new Wheel('wheel beand') ,new Wheel('wheel beand') ,new Wheel('wheel beand') ,new Wheel('wheel beand') )));
Run Code Online (Sandbox Code Playgroud)
看!更容易!您的代码也具有可读性和可重用性!
让我们考虑你有一个包含很多类的大项目,可能很多对象和类依赖于其他对象和类.
在工作哪个类取决于什么类和...这就是IOC进来并解决问题的地方时,你会记住这一点.
IOC模式或解决方案并不是很容易理解你应该面对我之前提到的问题.国际奥委会只是大项目!在java中Spring是最着名的IOC容器之一,IOC做了什么?它包含许多配置文件(或者一些IOC容器配置在源代码中,还有一些具有配置的用户界面),这个配置包含类和加载类之间的关系取决于哪个类对这也是一个很大的帮助懒加载!当你需要加载一个对象时,你加载它的依赖!