(IoC)Ioc—Inversion of Control,即“控制反转”
在传统的编程设计中,我们都是在通过在代码里 new
来创建对象实例,代码中如果类与类之间相互依赖的关系越多,就会在代码中 new
越多的依赖对象实例,但是这样会让代码设计紧耦合,不利于后续程序的开发和维护。
// 定义书本接口规范
interface Book
{
public function learn();
}
// 英文书
class EnglishBook implements Book
{
public function learn(){
echo 'learning...';
}
}
// 中文书
class ChineseBook implements Book
{
public function learn(){
echo '学习中...';
}
}
// 用户操作类
class User
{
protected $book;
public function __construct()
{
$this->book = new EnglishBook();
}
public function learn()
{
//开始学习
$this->book->learn();
}
}
$user = new User();
$user->learn();
控制反转(Inversion of Control)是一种是面向对象编程中的一种设计思想,用来减低计算机代码之间的耦合度。其基本思想是:借助于“第三方”实现具有依赖关系的对象之间的解耦。这个所谓的 “第三方”,也就是IoC容器。
以上实例代码过于耦合,也不符合开放封闭原则,如果我们要让用户此时学习中文的话,就要重新改类里的代码,不符合我们对代码解耦和要求,我们基于控制反转的思想,对以上的代码进行改造。
我们将不在操作类中直接创建依赖对象的实例,而是把依赖对象的创建交给外部,把依赖对象创建的控制权由原先在操作类中直接创建,转换成操作类外部控制操作类应该使用哪一个依赖对象,并注入到操作类中去。
这就是控制反转的思想。
谁控制谁,控制什么:我们直接在对象内部通过new进行创建对象,是程序主动去创建依赖对象;而IoC是有专门一个容器来创建这些对象,即由Ioc容器来控制对 象的创建;谁控制谁?当然是IoC 容器控制了对象;控制什么?那就是主要控制了外部资源获取(不只是对象包括比如文件等)。
为何是反转,哪些方面反转了:传统应用程序是由我们自己在对象中主动控制去直接获取依赖对象;而反转则是由容器来帮忙创建及注入依赖对象;为何是反转?因为由容器帮我们查找及注入依赖对象,对象只是被动的接受依赖对象,所以是反转;哪些方面反转了?依赖对象的获取被反转了。
// 定义书本接口规范
interface Book
{
public function learn();
}
// 英文书
class EnglishBook implements Book
{
public function learn(){
echo 'learning...';
}
}
// 中文书
class ChineseBook implements Book
{
public function learn(){
echo '学习中...';
}
}
// 用户操作类
class User
{
protected $book;
public function __construct(Book $book)
{
$this->book = $book;
}
public function learn()
{
//开始学习
$this->book->learn();
}
}
$user = new User(new ChineseBook());
$user->learn();
这样就不需要在User
类中写死依赖的Book
类,而是由第三方Ioc容器决定 将不同的Book
注入到 User
中去,User
只需要使用这个传入的依赖就好了,实现了控制反转同时,这也同时涉及到了依赖注入,我们会在下段去理解依赖注入
DI—Dependency Injection,即“依赖注入”
控制反转和依赖注入是相辅而成的,理解DI的关键是:“谁依赖谁,为什么需要依赖,谁注入谁,注入了什么”,那我们来深入分析一下:
●谁依赖于谁:当然是应用程序依赖于IoC容器;
●为什么需要依赖:应用程序需要IoC容器来提供对象需要的外部资源;
●谁注入谁:很明显是IoC容器注入应用程序某个对象,应用程序依赖的对象;
●注入了什么:就是注入某个对象所需要的外部资源(包括对象、资源、常量数据)。
我们通常会看到在一些框架中会经常用到这种设计,例如 Laravel
, 相同的,想要理解现代化的开发框架设计思路,就要理解好IoC/DI。Laravel
代码实现例子:
// routes/web.php
Route::get('/post/store', 'PostController@store');
// App\Http\Controllers
class PostController extends Controller {
public function store(Illuminate\Http\Request $request)
{
$this->validate($request, [
'category_id' => 'required',
'title' => 'required|max:255|min:4',
'body' => 'required|min:6',
]);
}
}
我们可以发现, Laravel
中使用依赖注入的时候,并不是像我们之前的例子,手动在外部创建依赖,而是直接定义好参数,在程序运行时就会自动实现依赖注入了,这种是如何实现的呢?
这里面其实用到了PHP 的 反射API,不懂的小伙伴可以查一下官方文档,学习一下。
反射的概念其实可以理解成根据类名返回该类的任何信息,比如该类有什么方法,参数,变量等等。我们先来学习下反射要用到的api。拿User举例
// 获取User的reflectionClass对象
$reflector = new reflectionClass(User::class);
// 拿到User的构造函数
$constructor = $reflector->getConstructor();
// 拿到User的构造函数的所有依赖参数
$dependencies = $constructor->getParameters();
// 创建user对象
$user = $reflector->newInstance();
// 创建user对象,需要传递参数的
$user = $reflector->newInstanceArgs($dependencies = []);
这时候我们可以创建一个make方法,传入User,利用反射机制拿到User的构造函数,进而得到构造函数的参数对象。用递归的方式创建参数依赖。最后调用newInstanceArgs方法生成User实例。 可能有些同学还不是很理解。下面我们用代码去简单模拟下
实现一个IoC容器
<?php
// 定义书本接口规范
interface Book
{
public function learn();
}
// 英文书
class EnglishBook implements Book
{
public function learn(){
echo 'learning...';
}
}
// 中文书
class ChineseBook implements Book
{
public function learn(){
echo '学习中...';
}
}
// 用户操作类
class User
{
protected $book;
public function __construct(Book $book)
{
$this->book = $book;
}
public function learn()
{
//开始学习
$this->book->learn();
}
}
class Ioc
{
public $binding = [];
public function bind($abstract, $concrete)
{
//这里为什么要返回一个closure呢?因为bind的时候还不需要创建User对象,所以采用closure等make的时候再创建;
$this->binding[$abstract]['concrete'] = function ($ioc) use ($concrete) {
return $ioc->build($concrete);
};
}
public function make($abstract)
{
// 根据key获取binding的值
$concrete = $this->binding[$abstract]['concrete'];
return $concrete($this);
}
// 创建对象
public function build($concrete)
{
$reflector = new ReflectionClass($concrete);
$constructor = $reflector->getConstructor();
if(is_null($constructor)) {
return $reflector->newInstance();
}else {
$dependencies = $constructor->getParameters();
$instances = $this->getDependencies($dependencies);
return $reflector->newInstanceArgs($instances);
}
}
// 获取参数的依赖
protected function getDependencies($paramters) {
$dependencies = [];
foreach ($paramters as $paramter) {
$dependencies[] = $this->make($paramter->getClass()->name);
}
return $dependencies;
}
}
//实例化IoC容器
$ioc = new Ioc();
$ioc->bind('Book',ChineseBook::class);
$ioc->bind('User',User::class);
$user = $ioc->make('User');
$user->learn();