前言
在面试过程中,我们常常会被问到框架的源代码和原理,而这对于刚刚毕业不久的开发人员,经常都是一脸懵逼,而这几乎离不开依赖注入和控制反转,几乎市面上的每种语言的每个框架都会实现了容器技术,为了提高代码的高内聚和低耦合,实现代码的高度复用,提高代码的可维护性和可扩展性,所以其重要性不言而喻,今天分分钟让你理解什么叫做依赖注入和控制反转,看完还不理解的,你尽管骂我渣男,反正我也不会承认的,哈哈哈。。。,铺垫到此结束,下面我们开始表演吧,
俗话说:talk is cheap,show me code,show time!!!
官方解释
控制反转(Inversion of Control,缩写为IoC),是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度。其中最常见的方式叫做依赖注入(Dependency Injection,简称DI),还有一种方式叫“依赖查找”(Dependency Lookup)。通过控制反转,对象在被创建的时候,由一个调控系统内所有对象的外界实体将其所依赖的对象的引用传递给它。也可以说,依赖被注入到对象中。
摘抄自百度百科
通俗案例
小明以前很穷,风餐露宿,居无定所。经过几年努力的搬砖加上运气终于发财了,自己也想拥有属于自己的房子,这个时候小明想,要不回家盖一栋房子,一来可以自己住,二来可以光宗耀祖,这个时候,小明需要自己去打造一栋房子;后来小明又想,为何不在城市直接买套房呢,生活更加丰富多彩也方便。于是,小明就找了房产中介(IOC容器)买了房子(依赖注入),最终小明很快就住上了自己喜欢的房子,开心快乐极了。。。
小明依赖房子,小明从自己盖房子(自己"控制房子")到找到中介买房子(让中介"控制房子"),这就叫做控制反转,也就是IOC;而房产中介根据小明的需求,直接把房子提供给小明(当然小明需要付钱了),这就叫做依赖注入,也就是DI。
当然,这个房子并不是房产中介建设的,而是开发商建设的吗,这个开发商就是服务提供者
三十年后,小明的这套房子格局跟不上时代了,住得不舒服,想改造/重新装修房子,但是时间成本太高了,于是,小明又找房产中介买了房子,小明又很快住上新房子了。。。这也体现了面向对象中类的单一职责原则。
传统写法
/**
* Class 购房者
*/
class 购房者
{
private $姓名;
public function __construct($姓名)
{
$this->姓名 = $姓名;
}
public function 买房()
{
$新房 = new 商品房('0001', '四室两厅', '180平方米');
echo '我是'.$this->姓名."\r\n";
echo '我买了'. $新房->获取户型(). '的房子了'."\r\n";
echo '我买了'. $新房->获取面积(). '的房子了'."\r\n";
}
}
/**
* Class 商品房
*/
class 商品房
{
private $房源编号;
private $户型;
private $面积;
public function __construct($房源编号, $户型, $面积)
{
$this->房源编号 = $房源编号;
$this->户型 = $户型;
$this->面积 = $面积;
}
public function 获取面积()
{
return $this->面积;
}
public function 获取户型()
{
return $this->户型;
}
}
$大明 = new 购房者('大明');
$大明->买房();
这点代码我相信非常容易理解,上述代码输出:
我是大明
我买了四室两厅的房子了
我买了180平方米的房子了
采用IOC和DI的思想来实现
/**
* Class 购房者
*/
class 购房者
{
private $姓名;
public function __construct($姓名)
{
$this->姓名 = $姓名;
}
public function 买房(商品房 $新房)
{
echo '我是'.$this->姓名."\r\n";
echo '我买了'. $新房->获取户型(). '的房子了'."\r\n";
echo '我买了'. $新房->获取面积(). '的房子了'."\r\n";
}
}
/**
* Class 商品房
*/
class 商品房
{
private $房源编号;
private $户型;
private $面积;
public function __construct($房源编号, $户型, $面积)
{
$this->房源编号 = $房源编号;
$this->户型 = $户型;
$this->面积 = $面积;
}
public function 获取面积()
{
return $this->面积;
}
public function 获取户型()
{
return $this->户型;
}
}
/**
* 房产中介,就是我们讲的ioc
* Class 房产中介
*/
class 房产中介
{
private $在售房源 = [];//这个类似于laravel的Container对象中的$bindings
private $认筹房源 = [];//类似于laravel的Container对象中的$resolved
private $公租房 = [];//类似于laravel的Container对象中的$instances
private $网红房源 = [];//类似于laravel的Container对象中的$aliases / $abstractAliases
private $意向购房群体 = [];
public function 预售登记($户型, $详细信息)
{
$this->在售房源[$户型] = $详细信息;
}
public function 获取在售房源($户型)
{
return ($this->在售房源[$户型])();//因为是闭包,所以,要增加()来执行闭包函数
}
public function 意向登记($意向人, $个人信息)
{
$this->意向购房群体[$意向人] = $个人信息;
}
public function 获取意向人信息($意向人)
{
return ($this->意向购房群体[$意向人])();//因为是闭包,所以,要增加()来执行闭包函数
}
}
$app = new 房产中介();
$app->预售登记('三室一厅', function(){
return new 商品房('1001', '三室一厅', '100平方米');
});
$app->预售登记('四室两厅', function(){
return new 商品房('1002', '四室两厅', '150平方米');
});
$app->意向登记('小明', function(){
return new 购房者('小明');
});
$app->意向登记('张三', function(){
return new 购房者('张三');
});
//echo $app->获取意向人信息('小明')->买房($app->获取在售房源('四室两厅'));
$意向人 = $app->获取意向人信息('小明');
$新房 = $app->获取在售房源('四室两厅');
$意向人->买房($新房);
上述代码输出:
我是小明
我买了四室两厅的房子了
我买了150平方米的房子了
对IOC和DI的本质分析
从上面的代码,我们可以看到,房产中介作为IOC,其实本质就是数组(可以是一维数组,也可以是多维数组)。
其实在laravel框架中,Container对象中的属性 b i n d i n g s 、 bindings、 bindings、resolved、 i n s t a n c e s 、 instances、 instances、aliases其实也是从不同维度来管理注册到容器中的对象的。
在上面的例子中,如果从业务逻辑角度来讲,无非就是购房者要买房,主要的类有,购房者、商品房。
如果按照传统代码来实现,那么购房者对象对商品房对象的依赖就是强依赖,因为在购房者类中需要new 商品房(),违反了软件设计的单一职责原则,后期不好维护和管理,随着项目越来越复杂,代码结构会越来越混乱。
而在采用IOC和DI的思想来实现的话,增加了房产中介对象这个IOC容器,商品房首先在房产中介那边进行预售登记,购房者也在房产中介那边进行一下意向登记,购房者对象需要依赖商品房对象,采用依赖注入,即:让房产中介直接把实例化后到商品房对象注入到购房者对象,购房者对象无需关注怎么实例化,只管拿过来用就行,换言之,购房者和商品房保持相对的独立,互不干扰,符合软件设计的单一职责原则。