1.依賴倒置(反轉)原則(DIP):一種軟件架構設計的原則(抽象概念,是一種思想)
在面向對象編程領域中,依賴反轉原則(Dependency inversion principle,DIP)是指一種特定的解耦(傳統的依賴關係建立在高層次上,而具體的策略設置則應用在低層次的模塊上)形式,使得高層次的模塊不依賴於低層次的模塊的實現細節,依賴關係被顛倒(反轉),從而使得低層次模塊依賴於高層次模塊的需求抽象。php
該原則規定:mysql
在上圖中,高層對象A依賴於底層對象B的實現;圖2中,把高層對象A對底層對象的需求抽象爲一個接口A,底層對象B實現了接口A,這就是依賴反轉。web
該原則顛倒了一部分人對於面向對象設計的認識方式。如高層次和低層次對象都應該依賴於相同的抽象接口。它轉換了依賴,高層模塊不依賴於低層模塊的實現,而低層模塊依賴於高層模塊定義的接口。通俗的講,就是高層模塊定義接口,低層模塊負責實現。spring
2.控制反轉(IoC):一種反轉流、依賴和接口的方式(DIP的具體實現方式,一種設計原則)
控制反轉(Inversion of Control,縮寫爲IoC),是面向對象編程中的一種設計原則,能夠用來減低計算機代碼之間的耦合度。其中最多見的方式叫作依賴注入(Dependency Injection,簡稱DI),還有一種方式叫「依賴查找」(Dependency Lookup)。經過控制反轉,對象在被建立的時候,由一個調控系統內全部對象的外界實體,將其所依賴的對象的引用傳遞給它。也能夠說,依賴被注入到對象中。sql
它把傳統上由程序代碼直接操控的對象的調用權交給容器,經過容器來實現對象組件的裝配和管理。所謂的「控制反轉」概念就是對組件對象控制權的轉移,從程序代碼自己轉移到了外部容器。編程
實現控制反轉主要有兩種方式:
1.依賴注入:
2.依賴查找數組
二者的區別在於,前者是被動的接收對象,在類A的實例建立過程當中即建立了依賴的B對象,經過類型或名稱來判斷將不一樣的對象注入到不一樣的屬性中,然後者是主動索取相應類型的對象,得到依賴對象的時間也能夠在代碼中自由控制。緩存
3.依賴注入(DI):IoC的一種實現方式,用來反轉依賴(IoC的具體實現方式)
依賴注入有以下實現方式:架構
3.依賴查找(DL):IoC的一種實現方式,用來反轉依賴(IoC的具體實現方式)
依賴查找更加主動,在須要的時候經過調用框架提供的方法來獲取對象,獲取時須要提供相關的配置文件路徑、key等信息來肯定獲取對象的狀態app
小結
依賴倒置原則(DIP):一種軟件架構設計的原則(抽象概念,一種思想)。
控制反轉(IoC):一種反轉流、依賴和接口的方式(DIP的具體實現方式)。
依賴注入(DI):IoC的一種實現方式,用來反轉依賴(IoC的具體實現方式)。
IoC容器(也稱DI Container):提供了動態地(自動化)建立、注入依賴單元,映射依賴關係等功能,減小了許多代碼量(DI框架)。
須要注意的一些地方
1.控制反轉的層面
在傳統的應用中,程序流程的順序是由開發者主導的,因爲IoC,主導權轉移到了框架的手裏(由於IoC容器)
2.控制反轉須要解決的問題
查找,生成所需的實例,返回給須要者(所以又叫依賴注入)
3.實現依賴注入的目的
儘管一個類A對它所依賴的類B是如何實現的一無所知,類A依然可以與類B通訊(經過定義一些通用接口)。類B在開發中可能會有多種實現,依賴注入(同時也是IoC)解決的問題就是自動地將這些類B的實如今須要的時候傳遞給類A。
4.如何實現依賴注入
最基本的思路是構造一個獨立的類,它的功能就是統一爲其餘全部類的依賴生成所需的實例(assembler,相似容器),而後構造並返回這個類
備註:對類A,類B,類C的定義以下
類A (須要經過容器獲取的)
類B (類A的依賴,廣義上的接口)
類C (類B的具體實現)
1.構造器注入
class MovieLister... (MovieLister至關於類A,MovieFinder至關於類B) public MovieLister(MovieFinder finder) { this.finder = finder; } class ColonMovieFinder... (這個至關於類c) public ColonMovieFinder(String filename) { this.filename = filename; } (這裏返回的pico就是IoC容器) private MutablePicoContainer configureContainer() { MutablePicoContainer pico = new DefaultPicoContainer(); Parameter[] finderParams = {new ConstantParameter("movies1.txt")}; //在使用容器前須要先配置,下面的代碼就是對容器的配置 pico.registerComponentImplementation(MovieFinder.class, ColonMovieFinder.class, finderParams); pico.registerComponentImplementation(MovieLister.class); return pico; } 下面是經過容器來得到類A的過程 public void testWithPico() { MutablePicoContainer pico = configureContainer();//得到一個配置好的容器 MovieLister lister = (MovieLister) pico.getComponentInstance(MovieLister.class);//經過容器來得到類A Movie[] movies = lister.moviesDirectedBy("Sergio Leone"); assertEquals("Once Upon a Time in the West", movies[0].getTitle()); }
2.setter注入
class MovieLister... (一樣的MovieLister爲類A,MovieFinder爲類B) private MovieFinder finder; public void setFinder(MovieFinder finder) { this.finder = finder; } class ColonMovieFinder... (這個一樣的至關於類C) public void setFilename(String filename) { this.filename = filename; } //下面是容器的配置 <beans> <bean id="MovieLister" class="spring.MovieLister"> <property name="finder"> <ref local="MovieFinder"/> </property> </bean> <bean id="MovieFinder" class="spring.ColonMovieFinder"> <property name="filename"> <value>movies1.txt</value> </property> </bean> </beans> public void testWithSpring() throws Exception { ApplicationContext ctx = new FileSystemXmlApplicationContext("spring.xml");//得到配置好的容器 MovieLister lister = (MovieLister) ctx.getBean("MovieLister"); //經過容器獲取類A Movie[] movies = lister.moviesDirectedBy("Sergio Leone"); assertEquals("Once Upon a Time in the West", movies[0].getTitle()); }
與DI相似,Service Locator也是用來打破依賴的
基本思想
提供一個獨立的類(即Service Locator),它可以爲整個應用提供所需的全部service(也能夠理解爲component)。
具體實現
1.在類A中,依賴的全部類都是經過Service Locator獲取的
MovieFinder finder = ServiceLocator.movieFinder();
2.經過配置能夠定製在Service Locator中實現如何返回一個特定實例,這個與DI相似
小結
1.實際上能夠將Service Locator和DI結合使用,在類A中經過Service Locator獲取依賴,而在Service Locator中則能夠經過DI來實現獲取具體的實例(或者將Service Locator與DI互換也能夠?)
2.動態的Service Locator:使用一張映射表,經過查表實現(或直接獲取)具體的實例
3.Service Locator與DI 的區別:使用Service Locator時是顯式地調用Locator,而Di並無顯式地調用
相關的類:
1.yii\di\instance
主要用在兩個地方:
對於 yii\di\Instance:
1.表示的是容器中的內容,表明的是對於實際對象的引用。
2.DI容器能夠經過他獲取所引用的實際對象。
3.Instance類僅有的一個屬性id通常表示的是實例的類型(即component ID, class name, interface name or alias name)。
2.yii\di\Container
注意:下面所說的「對象類型」的具體定義爲「類名,接口名,別名」
對於yiidiContainer
注意
1.在DI容器中,依賴關係的定義是惟一的。 後定義的同名依賴,會覆蓋前面定義好的依賴。
2.上面的鍵具體就是:帶命名空間的類名,接口名,或者是一個別名
3.對於 $_definitions 數組中的元素,它要麼是一個包含了」class」 元素的數組,要麼是一個PHP callable, 再要麼就是一個具體對象。這就是規範化後的最終結果
4.對於$_singletons數組中的元素,要不就是null(表示還未實例化),要不就是一個具體的實例
5.對於$_params數組中的元素,就是一個數組,包含構造函數的全部參數
6.對於$_reflections數組中的元素,就是一個ReflectionClass對象
7.setter注入能夠在實例化後
yiidiContainer使用的具體過程
一個簡單的例子
namespace app\models; use yii\base\Object; use yii\db\Connection; use yii\di\Container; interface UserFinderInterface { function findUser(); } class UserFinder extends Object implements UserFinderInterface { public $db; public function __construct(Connection $db, $config = []) { $this->db = $db; parent::__construct($config); } public function findUser() { } } class UserLister extends Object { public $finder; public function __construct(UserFinderInterface $finder, $config = []) { $this->finder = $finder; parent::__construct($config); } } $container = new Container; $container->set('yii\db\Connection', [ 'dsn' => '...', ]); $container->set('app\models\UserFinderInterface', [ 'class' => 'app\models\UserFinder', ]); $container->set('userLister', 'app\models\UserLister'); $lister = $container->get('userLister'); // which is equivalent to: $db = new \yii\db\Connection(['dsn' => '...']); $finder = new UserFinder($db); $lister = new UserLister($finder);
1.在類A的構造器參數列表中定義了該類全部須要被依賴注入的東西(類B)
2.註冊依賴:
a)yii\di\Container::set()
b)yii\di\Container::setSinglton()
使用到了$_definitions ,$_params, $_singletons
3.對象的實例化
a)解析依賴信息
yii\di\Container::getDependencies() (會被後續的build()調用)
getDependencies():操做$_reflections與$_dependencies
1.會向$_reflections 和 $_dependencies寫入信息
2.使用PHP的反射機制來獲取類的有關信息,主要就是爲了從構造器中獲取依賴信息,會將反射獲得的信息寫入$_reflections
3.將從構造器中獲取的依賴信息(即構造函數的參數列表)寫入$_dependencies
4.返回值: 數組[$reflection, $dependencies]
yii\di\Container::resolveDependencies() (一樣的會被後續的build()調用)
resolveDependencies()利用getDependencies()得到的信息進一步具體處理(遞歸調用)。處理依賴信息, 將依賴信息中保存的Instance實例所引用的類或接口進行實例化。
b)建立實例
yii\di\Container::build()
由getDependencies()得到第一層依賴
由resolveDependencies()遞歸分析依賴,最終生成全部依賴的實例
$reflection->newInstanceArgs($dependencies);//生成全部依賴後生成這個實例
注意:DI容器只支持 yiibaseObject 類,也就是說若是你想你的類能夠放在DI容器裏,那麼必須繼承自 yiibaseObject 類。
4.獲取依賴實例化對象
yii\di\Container::get() a)若是是已經實例化的單例,直接返回($_singletons) b)若是是還沒有定義(不存在於$_definition),則說明其實例化沒有依賴,調用build() c)存在$_definition i.$definition爲callable,直接調用 ii.$definition爲數組,根據$definition數組中的‘class’,遞歸調用get(),遞歸終止的條件是(當具體實現類就是當前的依賴類時),遞歸結束時調用build()進行實例化 iii.$definition爲對象,直接返回該對象,並將該對象設置爲單例
setSinglton()相似 public function set($class, $definition = [], array $params = []) { //normalizeDefinition()處理後,返回值要麼是一個包含了」class」 元素的數組,要麼是一個PHP callable, 再要麼就是一個具體對象 $this->_definitions[$class] = $this->normalizeDefinition($class, $definition); $this->_params[$class] = $params; unset($this->_singletons[$class]); return $this; } public function get($class, $params = [], $config = []) { if (isset($this->_singletons[$class])) {//是單例,且已經實例化(不爲null) // singleton return $this->_singletons[$class]; } elseif (!isset($this->_definitions[$class])) {//尚未定義過,須要build return $this->build($class, $params, $config); } $definition = $this->_definitions[$class]; if (is_callable($definition, true)) { $params = $this->resolveDependencies($this->mergeParams($class, $params)); $object = call_user_func($definition, $this, $params, $config); } elseif (is_array($definition)) { $concrete = $definition['class']; unset($definition['class']); $config = array_merge($definition, $config); $params = $this->mergeParams($class, $params); if ($concrete === $class) {//$concrete至關於以前提到的具體實現類C,而$class則至關於接口類B $object = $this->build($class, $params, $config); } else { $object = $this->get($concrete, $params, $config);//遞歸,直到找到具體的實現類C } } elseif (is_object($definition)) { return $this->_singletons[$class] = $definition; } else { throw new InvalidConfigException('Unexpected object definition type: ' . gettype($definition)); } if (array_key_exists($class, $this->_singletons)) { // singleton $this->_singletons[$class] = $object; } return $object; } protected function build($class, $params, $config) { /* @var $reflection ReflectionClass */ list ($reflection, $dependencies) = $this->getDependencies($class); //獲取第一層依賴關係 foreach ($params as $index => $param) { $dependencies[$index] = $param; //額外提供的構造函數參數,添加到依賴中 } $dependencies = $this->resolveDependencies($dependencies, $reflection);//遞歸解析依賴,並會在此返回依賴的實例 if (!$reflection->isInstantiable()) { throw new NotInstantiableException($reflection->name); } if (empty($config)) { return $reflection->newInstanceArgs($dependencies);//經過反射實例生成對象 } //config中的對象做爲該類的property使用 if (!empty($dependencies) && $reflection->implementsInterface('yii\base\Configurable')) { // set $config as the last parameter (existing one will be overwritten) $dependencies[count($dependencies) - 1] = $config; return $reflection->newInstanceArgs($dependencies); } else { $object = $reflection->newInstanceArgs($dependencies); foreach ($config as $name => $value) { $object->$name = $value; } return $object; } } protected function getDependencies($class) { if (isset($this->_reflections[$class])) {//若是已經反射解析過則直接返回 return [$this->_reflections[$class], $this->_dependencies[$class]]; } $dependencies = []; $reflection = new ReflectionClass($class); //構造函數的參數即這個類的依賴 $constructor = $reflection->getConstructor(); if ($constructor !== null) { foreach ($constructor->getParameters() as $param) { if ($param->isDefaultValueAvailable()) { $dependencies[] = $param->getDefaultValue(); } else { $c = $param->getClass();//這裏要能獲取到類名須要在構造函數中用類型限制參數,不然獲取到null,並且注意對於php的基本類型,獲取到的也是null $dependencies[] = Instance::of($c === null ? null : $c->getName()); } } } $this->_reflections[$class] = $reflection; $this->_dependencies[$class] = $dependencies; return [$reflection, $dependencies]; } protected function resolveDependencies($dependencies, $reflection = null) { foreach ($dependencies as $index => $dependency) { if ($dependency instanceof Instance) { if ($dependency->id !== null) { //這裏的dependency是Instance的實例 $dependencies[$index] = $this->get($dependency->id); } elseif ($reflection !== null) { $name = $reflection->getConstructor()->getParameters()[$index]->getName(); $class = $reflection->getName(); throw new InvalidConfigException("Missing required parameter \"$name\" when instantiating \"$class\"."); } } } return $dependencies; }
遞歸調用的示意圖
先看一下各個類的繼承關係
下面以Yii::$app->db爲例
1.配置組件
配置的內容:
'components' => [ 'db' => [ 'class' => 'yii\db\Connection', 'dsn' => 'mysql:host=localhost;dbname=wechat', 'username' => 'root', 'password' => 'michael', 'charset' => 'utf8', ],
2.在框架的啓動過程當中加載組件的定義
Yii2的啓動
入口腳本:
(new yii\web\Application($config))->run(); 1.new yii\web\Application($config) 2.run()
yii\web\Application的構造函數
public function __construct($config = []) { Yii::$app = $this; static::setInstance($this); //將當前module存到Yii::$app->loadedModules[] $this->state = self::STATE_BEGIN; //1.經過$config配置別名,基本參數 //2.配置核心組件(僅僅是配置,將Yii框架寫好的配置與本身的配置合併) $this->preInit($config); $this->registerErrorHandler($config); //下面這一行是重點,Component是當前類的祖先 //在下面的構造函數中執行了Yii::configure($this, $config),將$config中的配置做爲屬性添加到$app中 Component::__construct($config); }
Component::__construct($config)
//實際上下面這個構造函數的定義在yii\base\Object中 public function __construct($config = []) { if (!empty($config)) { Yii::configure($this, $config); } $this->init(); }
Yii::configure($this, $config)
public static function configure($object, $properties) { foreach ($properties as $name => $value) { //下面這行代碼會觸發魔術方法 ($object->components = $value) //實際執行的代碼是ServiceLocator::setComponents($components) $object->$name = $value; } return $object; }
ServiceLocator::setComponents($components)
public function setComponents($components) { foreach ($components as $id => $component) { $this->set($id, $component); } }
最終加載組件配置的代碼
//從下面的代碼中能夠看到,最終組件的配置被存儲在$app->_definitions數組中 public function set($id, $definition) { if ($definition === null) { unset($this->_components[$id], $this->_definitions[$id]); return; } unset($this->_components[$id]); if (is_object($definition) || is_callable($definition, true)) { // an object, a class name, or a PHP callable $this->_definitions[$id] = $definition; } elseif (is_array($definition)) { // a configuration array if (isset($definition['class'])) { $this->_definitions[$id] = $definition; } else { throw new InvalidConfigException("The configuration for the \"$id\" component must contain a \"class\" element."); } } else { throw new InvalidConfigException("Unexpected configuration type for the \"$id\" component: " . gettype($definition)); } }
3.獲取組件
Yii::$app->db會觸發魔術方法,調用ServiceLocator::__get()
public function __get($name) { if ($this->has($name)) { //已經定義過組件 return $this->get($name); } else { //沒有定義過組件 return parent::__get($name); } } public function has($id, $checkInstance = false) { //這裏由於在框架啓動過程當中將組件的配置加載到$_definitions中了,因此會返回true return $checkInstance ? isset($this->_components[$id]) : isset($this->_definitions[$id]); } //經過下面的代碼能夠看到,若是組件已經實例化過存儲在$_components中了,就直接返回 //不然經過Yii::createObject($definition)來生成組件實例,並存儲到$_components中 public function get($id, $throwException = true) { if (isset($this->_components[$id])) { return $this->_components[$id]; } if (isset($this->_definitions[$id])) { $definition = $this->_definitions[$id]; if (is_object($definition) && !$definition instanceof Closure) { return $this->_components[$id] = $definition; } else { return $this->_components[$id] = Yii::createObject($definition); } } elseif ($throwException) { throw new InvalidConfigException("Unknown component ID: $id"); } else { return null; } }
Yii::createObject($definition)
//在Yii2框架中要使用DI來生成對象的話,能夠經過調用Yii::createObject($definition)實現 public static function createObject($type, array $params = []) { if (is_string($type)) { //使用容器 return static::$container->get($type, $params); } elseif (is_array($type) && isset($type['class'])) { $class = $type['class']; unset($type['class']); return static::$container->get($class, $params, $type); } elseif (is_callable($type, true)) { return static::$container->invoke($type, $params); } elseif (is_array($type)) { throw new InvalidConfigException('Object configuration must be an array containing a "class" element.'); } throw new InvalidConfigException('Unsupported configuration type: ' . gettype($type)); }