这2个其实都算得上是一种设计模式或者说是一种软件设计思想,目的都是为了增加软件可维护性和扩展性,比如在Java Web框架SpringMVC 和PHP Web框架laravel里面都有应用。
首先得理解什么叫依赖?从宏观上看,得益于开源软件运行的兴起,很多时候我们写项目并不是什么都是从零开始,我们往往会利用很多现成的开源代码进行快速开发,能不重复造轮子最好,所以我们往往依赖很多开源组件。gradle、npm、composer 等工具的部分功能就是解决项目依赖问题。
class FileSession { private $file; ... more code public function set($name, $value) { echo "set $name = $value into $this->file\n"; } public function get($name) { echo "get $name value\n"; } }service类:
class SessionService { private $sessionHandler; // 堆代码 duidaima.com public function __construct() { $this->sessionHandler = new FileSession(); } public function set($name, $value) { $this->sessionHandler->set($name, $value); } public function get($name) { return $this->sessionHandler->get($name); } ...more code }在这种普通写法里面,当我们需要一个 sessionHandler 的时候我们是直接在构造函数里面实例化,这样没啥问题,确实解决了依赖问题。但是依赖注入的另一个词“注入”更强调的是一种从外部而来的,而不是内部。
class SessionService { private $sessionHandler; public function __construct($sessionHandler) { $this->sessionHandler = $sessionHandler; } public function set($name, $value) { $this->sessionHandler->set($name, $value); } public function get($name) { return $this->sessionHandler->get($name); } ...more code }这种写法要求你在使用service的时候从外部传入一个handler,这就实现了依赖注入,注入的方式有很多种,刚才这种可以称之为构造器注入,还有一种叫setter注入,比如说,我们可以在service里面里面提供一个setter函数用于设置所需的handler:
public function setSessionHandler($sessionHandler) { $this->sessionHandler = $sessionHandler }这种写法有哪些好处呢?一个是解耦,假如说这个FileSession实例化的时候还需要其它操作,比如传入一个配置参数,原本的写法可能就需要更改service类了,在构造函数里面啪啪啪写一堆。还有就是方便测试,既然解耦了就可以很方便的进行单元测试。另一个是控制反转,就是说这个FileSession外部传入的,是service类无法控制的,也就说控制权在于外部。
interface SessionHandler { public function set($name, $value); public function get($name); }我们约定,只要你实现了这个接口,你就可以当一个sessionHandler,你就可以用来处理session,至于你怎么实现,service不管,比如说我们换一个redis:
class RedisHandler implments SessionHandler { private $redisInstance; public function __construct() { $this->redisInstance = new Redis(); } public function set($name, $value) { $this->redisInstance->set($name, $value); } public function get($name) { return $this->redisInstance->get($name); } }这时候我们可以在service的构造函数稍作修改,增加一个类型约束:
public function __construct(SessionHandler $sessionHandler) { $this->sessionHandler = $sessionHandler; }这样的设计之后,好处显而易见,我们可以很轻松替换掉之前的fileSession,不改动service的一行代码,只要按照sessionHandler的接口去实现相应的方法就行,在laravel里面这样的接口就叫做 Contracts,下面就是框架里面的Cache缓存的 Contracts:
<?php namespace Illuminate\Contracts\Cache; interface Store { /** * Retrieve an item from the cache by key. * * @param string|array $key * @return mixed */ public function get($key); /** * Retrieve multiple items from the cache by key. * * Items not found in the cache will have a null value. * * @param array $keys * @return array */ public function many(array $keys); /** * Store an item in the cache for a given number of minutes. * * @param string $key * @param mixed $value * @param float|int $minutes * @return void */ public function put($key, $value, $minutes); /** * Store multiple items in the cache for a given number of minutes. * * @param array $values * @param float|int $minutes * @return void */ public function putMany(array $values, $minutes); ... more code }据我看到的,在laravel框架里面自带了至少5种实现,分别是Array、File、Database、Memcached、Redis, 如果你愿意你也可以自己去实现这个 Contracts,然后替换到框架里面的,不过框架本身实现的已经非常优秀了,除非你写的更好,一般情况下不需要这样做,但是laravel提供了这种可能。
public function comment(Post $post, Request $request) { $this->validate($request, [ 'content' => 'required|min:5' ]); $comment = new Comment([ 'content' => $request->get('content'), 'user_id' => auth()->user()->id, 'post_id' => $post->id, ]); $post->comments()->save($comment); return redirect()->back(); }我们只需要在方法的参数上面标明所需的方法,就可以在代码直接用了,ioc容器替我们自动注入了依赖!