您的位置 首页 php

IoC容器介绍——深入理解IoC容器思想(一)

3b030000f0e17127e9eb

IoC思想

IoC:英文全称:Inversion of Control,中文名称:控制反转。

IoC核心思想是通过IoC容器管理对象的生成、资源取得、销毁等生命周期,在IoC容器中建立对象与对象之间的依赖关系,IoC容器启动后,所有对象直接取用,不再使用new操作符产生对象和建立对象之间的依赖关系。

在开始讲解之前,首先明确一件事:IoC不是一种编程技术,它是一种编程模型,一种优雅的编程思想,它有多重实现方式,其中主要的实现方式就是依赖注入(Dependency Injection,简称DI)。如果您正在阅读相关框架源码,或者正在写一个自己的框架,不妨花半个小时阅读一下这篇文章,读完这篇文章你或许会对某些框架的源码细节茅塞顿开,你也可以将IoC的思想运用在你自己的框架中。

面向对象思想三部曲

第一阶段:在很久以前,我们的祖先(调用者)想吃什么水果(被调用者),需要自己去摘。

第二阶段:近代社会,开始出现超市。我们想吃什么水果去超市购买即可,这里的超市相当于接口或工厂,水果是被调用者实例。(面向接口编程和 工厂模式 都属于这个阶段),实际上是依赖转移。

第三阶段:在现代社会,我们想吃水果,外卖公司可以直接送到我们面前,我们只需要享受吃的过程即可。

当一个类的实例需要调用另一个类的实例实现某一功能时,传统的方式(第一阶段)通常是由调用者创建被调用者实例,这种情况下调用者和被调用者之间耦合非常紧密,不利于业务 解耦 。随着工厂模式(第二阶段)的出现,调用者只需要从特定工厂获取被调用者实例,这种情况下调用者仍然需要与特定的工厂耦合。

第三阶段,”我们需要的物品都有外卖公司管理,外卖公司有办法将我们需要的物品交到我们手上”,创建被调用者实例的工作不再由调用者完成,而是由IoC容器创建。也就是说,创建被调用者实例的控制权被反转了,这就是我们常说的控制反转。创建被调用者实例的工作交由IoC容器完成,IoC容器提供方法将被调用者实例的控制权注入给调用者,因此这种方式也叫依赖注入。

引子

小明的技术经理有一个需求:实现一个PHP框架的存储层,框架要支持Mysql和 mongodb

为了实现经理的需求,小明在框架中封装了两个类:

Mysql类

<?phpclass Mysql {  private  $storge = []; function __construct() { echo __CLASS__."\n"; } /** * 添加 * @param * @return bool */ public function insert($data) { echo __METHOD__."\n"; $this->storge[$data['id']] = $data; } /** * 查找 * @param * @return bool */ public function select($id) { echo __METHOD__."\n"; return $this->storge[$id]; }} 

Mongodb类

<?phpclass Mongodb { private $storge = []; function __construct() { echo __CLASS__."\n"; } /** * 添加 * @param * @return bool */ public function insert($data) { echo __METHOD__."\n"; $this->storge[$data['id']] = $data; } /** * 查找 * @param * @return bool */ public function select($id) { echo __METHOD__."\n"; return $this->storge[$id]; }} 

以上两个类封装完了,创建一个Article类使用这个存储层

<?phpclass Article { function __construct() { echo __CLASS__."\n"; $this->mysql = new Mysql(); } /** * 添加文章到mysql数据库 * @param * @return */ public function add($data) { echo __METHOD__."\n"; $ret = $this->mysql->insert($data); return $ret; } /** * 从mysql数据库中获取数据 * @param int * @return array */ public function get($id) { echo __METHOD__."\n"; $ret = $this->mysql->select($id); return $ret; }}$article = new Article();$data = array( 'id' => 1, 'title' => 'welcome to my page', 'content' => 'this is a article that introduce IoC, if you are inserest in it, you can read it', 'time' => time(),);$article->add($data);print_r($article->get($data['id'])); 
$ php Article.php 

执行结果:

ArticleMysqlArticle::addMysql::insertArticle::getMysql::selectArray( [id] => 1 [title] => welcome to my page [content] => this is a article that introduce IoC, if you are inserest in it, you can read it [time] => 1504519778) 

这种做法就是我们所说的”第一阶段”,由调用者Article去实例化被调用者Mysql,这种做法的缺点很明显,Article和Mysql耦合太紧密,程序可扩展性很差,如果使用mongodb存储文章,需要修改Article中的代码。

如何实现Article与Mysql之间的解耦呢?

通过观察发现,Mysql和Mongodb有相同的方法,我们可以抽象一层接口,让Mysql和Mongodb继承该接口,代码如下:

抽象接口类Storage

<?phpinterface Storage { public function insert($data); public function select($id);} 

改造后的Mysql类

<?phpclass Mysql implements Storage { private $storge = []; function __construct() { echo __CLASS__."\n"; } /** * 添加 * @param * @return bool */ public function insert($data) { echo __METHOD__."\n"; $this->storge[$data['id']] = $data; } /** * 查找 * @param * @return bool */ public function select($id) { echo __METHOD__."\n"; return $this->storge[$id]; }} 

改造后的Mongodb类

<?phpclass Mongodb implements Storage { private $storge = []; function __construct() { echo __CLASS__."\n"; } /** * 添加 * @param * @return bool */ public function insert($data) { echo __METHOD__."\n"; $this->storge[$data['id']] = $data; } /** * 查找 * @param * @return bool */ public function select($id) { echo __METHOD__."\n"; return $this->storge[$id]; }} 

改造后的Article调用存储层代码:

<?phpclass Article { function __construct(Storage $storage) { echo __CLASS__."\n"; $this->storage = $storage; } /** * 添加文章到mysql数据库 * @param * @return */ public function add($data) { echo __METHOD__."\n"; $ret = $this->storage->insert($data); return $ret; } /** * 从mysql数据库中获取数据 * @param int * @return array */ public function get($id) { echo __METHOD__."\n"; $ret = $this->storage->select($id); return $ret; }}$article = new Article(new Mysql());$data = array( 'id' => 1, 'title' => 'welcome to my page', 'content' => 'this is a article that introduce IoC, if you are inserest in it, you can read it', 'time' => time(),);$article->add($data);print_r($article->get($data['id'])); 

如果我们想用Mongodb存储数据,只需要修改:

$article = new Article(new Mongodb()); 

这种方式处于“第二阶段”,面向接口编程屏蔽了具体类的调用,但是调用者与接口仍然耦合。

对于工厂模式,也避免不了调用者与特定工厂类的耦合,网上有很多关于工厂模式的介绍,这里不介绍了。

思考一下如何让Article与Storage接口解耦呢?

实现Article与Storage接口的解耦,需要用到依赖注入思想,把实例之间的依赖关系交给IoC容器管理。

实现我们的IoC容器

<?phpclass Container{ protected $bindings = []; public function bind($name, $definition) { $this->bindings[$name] = $definition; } public function make($name) { if (isset($this-> bind ings[$name])) { $definition = $this->bindings[$name]; } else { throw new Exception("Service '" . name . "' wasn't found in the dependency injection container"); } if (is_object($definition)) { $instance = call_user_func($definition); } else { return new $definition(); } return $instance; }} 

使用ioc容器之后的Article代码:

<?phpclass Article { function __construct($storage) { echo __CLASS__."\n"; $this->storage = $storage; } /** * 添加文章到mysql数据库 * @param * @return */ public function add($data) { echo __METHOD__."\n"; $ret = $this->storage->insert($data); return $ret; } /** * 从mysql数据库中获取数据 * @param int * @return array */ public function get($id) { echo __METHOD__."\n"; $ret = $this->storage->select($id); return $ret; }}$ioc = new Container();// 提供两种绑定形式,闭包绑定和类名绑定$ioc->bind('mysql', function() { return new Mysql();});$ioc->bind('mongodb', function() { return new Mongodb();});//$ioc->bind('mongodb', Mongodb::class);//$ioc->bind('mysql', Mysql::class);$article = new Article($ioc->make('mongodb'));$data = array( 'id' => 1, 'title' => 'welcome to my page', 'content' => 'this is a article that introduce IoC, if you are inserest in it, you can read it', 'time' => time(),);$article->add($data);print_r($article->get($data['id'])); 

执行

$ php Article.php 

执行结果:

MongodbArticleArticle::addMongodb::insertArticle::getMongodb::selectArray( [id] => 1 [title] => welcome to my page [content] => this is a article that introduce IoC, if you are inserest in it, you can read it [time] => 1504529061) 

通过引入IoC容器,调用者不需要再创建被调用者实例,而是交由IoC创建。现在流行的开发框架大多采用IoC容器,开发者只需要专注于业务,无须再花费精力维护类实例的创建、类与类之间的依赖关系,提高了开发效率。

以上实现了简单的IoC容器,我们是手动将实例绑定到容器中,高级的IoC容器在服务初始化时会将需要的类注入到容器中,laravel中IoC容器是通过反射实现类的实例化,核心代码如下:

public function build($concrete, array $parameters = []) { // If the concrete type is actually a Closure, we will just execute it and // hand back the results of the functions, which allows functions to be // used as resolvers for more fine-tuned resolution of these objects. if ($concrete instanceof Closure) { return $concrete($this, $parameters); } $reflector = new ReflectionClass($concrete); // If the type is not instantiable, the developer is attempting to resolve // an abstract type such as an Interface of Abstract Class and there is // no binding registered for the abstractions so we need to bail out. if (! $reflector->isInstantiable()) { if (! empty($this->buildStack)) { $previous = implode(', ', $this->buildStack); $ message  = "Target [$concrete] is not instantiable while building [$previous]."; } else { $message = "Target [$concrete] is not instantiable."; } throw new BindingResolutionException($message); } $this->buildStack[] = $concrete; $constructor = $reflector->getConstructor(); // If there are no constructors, that means there are no dependencies then // we can just resolve the instances of the objects right away, without // resolving any other types or dependencies out of these containers. if (is_null($constructor)) { array_pop($this->buildStack); return new $concrete; } $dependencies = $constructor->getParameters(); // Once we have all the constructor's parameters we can create each of the // dependency instances and then use the reflection instances to make a // new instance of this class, injecting the created dependencies in. $parameters = $this->keyParametersByArgument( $dependencies, $parameters ); $instances = $this->getDependencies( $dependencies, $parameters ); array_pop($this->buildStack); return $reflector->newInstanceArgs($instances); } 

以上介绍了IoC容器的核心思想,下一讲一起探讨如何在IoC容器中实现单例绑定和类依赖关系绑定。

文章来源:智云一二三科技

文章标题:IoC容器介绍——深入理解IoC容器思想(一)

文章地址:https://www.zhihuclub.com/32372.shtml

关于作者: 智云科技

热门文章

网站地图