任何人都能用简单易懂的单词解释依赖注入和IoC容器,因为我是初学者。感谢
答案 0 :(得分:117)
答案变得比我想要的更长。我提供了一些背景信息。如果您正在寻找短期解释,请阅读IoC-Container的第一段和粗体段落。
依赖注入是一种设计模式,它遵循名称所述的内容。它将对象注入构造函数或其他对象的方法,以便一个对象依赖于一个或多个其他对象。
<?php
class DatabaseWriter {
protected $db;
public function __construct(DatabaseAdapter $db)
{
$this->db = $db;
}
public function write()
{
$this->db->query('...');
}
}
您可以看到我们需要传递类构造函数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
可以在没有任何依赖性的情况下实例化对象。有一种方法可以注入可以被选择调用的依赖项(setLogger()
)。现在,它是否依赖于方法实现来使用依赖性(如果它没有设置)。
值得指出的是对Setter Injection持谨慎态度。调用方法或访问尚未注入的依赖项上的属性将导致讨厌的Fatal error: Call to a member function XXX() on a non-object
。因此,每次访问依赖项时,必须先对其进行空值检查。更简洁的方法是使用Null Object Pattern并将依赖项移动到构造函数中(作为可选参数,如果没有传递,则在类中创建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);
接口注入的意义是有争议的。我个人从未使用过它。我更喜欢构造函数注入。这使我能够完成完全相同的任务,而无需在喷射器侧使用额外的接口。
我们也可以依赖于抽象,而不是依赖于具体的实例。
<?php
class DatabaseWriter {
protected $db;
public function __construct(DatabaseAdapterInterface $db)
{
$this->db = $db;
}
public function write()
{
$this->db->query('...');
}
}
现在我们inverted the control。我们现在可以注入任何使用type hinted接口的实例,而不是依赖于具体的实例。接口注意后面的具体实例实现了我们将要使用的所有方法,这样我们仍然可以依赖它们在依赖类中。
确保获得那个,因为它是IoC-Container的核心概念。
在最短的时间内我可以想到我会像那样描述IoC容器:
IoC-Container是一个知道如何创建实例并知道所有它们的底层依赖关系以及如何解决它们的组件。
如果我们采用上面的示例,假设DatabaseAdapter
本身拥有它自己的依赖项。
class ConcreteDatabaseAdapter implements DatabaseAdapterInterface{
protected $driver;
public function __construct(DatabaseDriverInterface $driver)
{
$this->driver = $driver;
}
}
因此,为了能够使用DatabaseAdapter
,您需要将DatabaseDriverInterface
抽象的实例作为依赖项传递。
但是你的DatabaseWriter
班级不了解它,也不应该。 DatabaseWriter
不应关心DatabaseAdapter
如何工作,它应该只关心传递DatabaseAdapter
而不是如何创建它。这就是IoC-Container派上用场的地方。
App::bind('DatabaseWriter', function(){
return new DatabaseWriter(
new ConcreteDatabaseAdapter(new ConcreteDatabaseDriver)
);
});
正如我已经说过的那样,DatabaseWriter
本身并不了解它的依赖关系。但是IoC-Container知道它们的全部内容并且知道在哪里找到它们。因此,当最终要实例化DatabaseWriter
类时,首先要求IoC-Container 如何实例化。这就是IoC-Container所做的事情。
简单地说(如果你涉及设计模式)。它是依赖注入容器(我已经在上面解释过)和Service Locator的组合。
答案 1 :(得分:10)
考虑你有一个类似下面的汽车类:
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
}
}
现在考虑重复使用你的代码,例如你想拥有另一个适用于丰田公司的汽车类。 你应该在上面编写完全代码,只需进行一些编辑,如下所示:
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
}
}
如果不使用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
}
}
现在很容易就可以使用以下Car类创建每个汽车对象:
$BMW = new Car (new Engine('BMW' , array(new Wheel('wheel beand') ,new Wheel('wheel beand') ,new Wheel('wheel beand') ,new Wheel('wheel beand') )));
看到了!
更容易!您的代码也具有可读性和可重用性!
让我们考虑你有一个包含很多类的大项目,可能很多对象和类依赖于其他对象和类。
在工作哪个班级取决于什么班级和......时,你会记住这一点。 这就是国际奥委会的所在并解决问题。
IOC模式或解决方案并不是很容易理解你应该面对我之前提到的问题。 国际奥委会只是大项目! 在java中Spring是最着名的IOC容器之一 和国际奥委会做了什么? 它包含许多配置文件(或者一些IOC容器配置在源代码中,还有一些具有配置的用户界面) 这个配置包含类和加载类之间的关系取决于哪些类 这也是延迟加载的一大帮助! 当你需要加载一个对象时,你加载它的依赖!