相信很多人聽說過依賴注入,依賴注入實現的基礎條件離不開容器,容器就是用來管理類依賴和注入的,負責服務的管理和解耦元件,最簡單的理解我們可以把容器理解成一個超級大、專門存物件的陣列。
如圖所示呼叫者通過容器的標示獲取到物件範例,圖裡可以看出來,可以通過 ::class 的方式來獲取也可以直接通過物件標示獲取範例物件。
大家可能都聽說過IOC容器,IOC的全稱是:(Inversion Of Control,反轉控制)。
我們來理解一下什麼是反轉控制,在我們傳統編碼中我們在類與類之間的依賴通常是我們通過編碼的方式new出來物件再傳遞的,而使用控制反轉我們可以把物件的控制權交給容器或者框架去實現。目的是為了讓我們不需要寫死去建立物件,看圖1可以知道,容器裡面存放著很多物件,當我們要使用的時候可以直接去用。而容器裡面的物件不需要我們在程式碼中編碼建立。在需要某個類物件的時候會去容器裡面獲取物件,如果物件不存在則會自動建立。這就是省略了我們在程式碼裡面去建立物件的過程,由容器去幫我們實現這個建立的過程,這就叫反轉控制。一句話總結IOC:把建立物件的控制權轉移給容器實現類的範例化。
例如:沒有使用IOC的情況下,我們想要建立類
<?php
class Sunny{
}
$sunny = new Sunny();
登入後複製
我們需要手動去new一個類,這種情況就是寫死在程式碼裡面去實現的。
而使用IOC容器的程式碼則可以這樣寫。
<?php
class Sunny{
}
$sunny = Container::getBean(Sunny::class);
登入後複製
在容器的內部去幫我們實現這個類,有同學看到這裡可能會有疑問,我使用 new Sunny 不是程式碼寫得更短更簡單嗎?我們看完依賴注入再看一個例子。
現在知道了IOC是什麼,那麼一個新的問題出來了,我們在建立類的時候有些類的構造方法會需要我們傳遞引數怎麼辦?通過IOC的學習我們知道了IOC容器會幫我們解決這個物件範例建立的問題,那麼在容器裡面建立物件的時候發現類有其他依賴則會進行依賴查詢,容器尋找需要物件的過程,稱為DL(Dependency Lookup, 依賴查詢)。而把需要的依賴注入到程式碼片段中這個稱為DI(Dependency Injection,依賴注入)。
例如IOC裡面說到的 new Sunny 這個例子。如果在類與類之間有多重依賴。
<?php
class Computer{
public function run(){
echo "程式設計中....\n";
}
}
class Sunny{
private $computer;
public function __construct(Computer $computer){
$this->computer = $computer;
}
public function program(){
$this->computer->run();
}
}
$sunny = new Sunny(new Computer());
$sunny->program();
登入後複製
這裡可以看到 Sunny 這個類想要程式設計依賴類 Computer 這個類,而如果使用IOC容器實現依賴注入的話,程式碼就簡單了。
<?php
class Computer{
public function run(){
echo "程式設計中....\n";
}
}
class Sunny{
private $computer;
public function __construct(Computer $computer){
$this->computer = $computer;
}
public function program(){
$this->computer->run();
}
}
$sunny = Container::getBean(Sunny::class);
$sunny->program();
登入後複製
一句話總結:解決建立類範例當中對其他類的依賴,動態的向某個物件提供它所需要的其他物件。
依賴倒置解決的問題是鬆耦各個模組之間的重度依賴,上層模組不應該依賴底層模組,它們都應該依賴於抽象。通常簡單的理解依賴倒置就是面向介面或者面向抽象來進行程式設計。我們通過下面的例子來看看面向介面程式設計。
class Cache{
public function set($key,$value){
$redis = new CFile();
$redis->set($key,$value);
}
}
class CFile{
public function set($key,$value){
echo "file:{$key}->{$value}\n";
}
}
$cache = new Cache();
$cache->set("name","sunny");
登入後複製
上面的這段程式碼看似沒有什麼大問題,但是如果有一天把檔案快取改成Redis快取呢?
class Cache{
public function set($key,$value){
$redis = new CRedis();
$redis->set($key,$value);
}
}
class CRedis{
public function set($key,$value){
echo "redis:{$key}->{$value}\n";
}
}
$cache = new Cache();
$cache->set("name","sunny");
登入後複製
通過這段程式碼可以看出來當一個快取使用的驅動改變了的時候,Cache的程式碼也必須作出相應的改變,因為程式碼寫死在呼叫者身上了,耦合度變得高了。再對程式碼進行改造一樣,讓程式設計師面向interface程式設計,讓程式碼變得更通用,更規範。
interface ICache{
public function set($key,$value);
}
class CRedis implements ICache {
public function set($key,$value)
{
echo "redis:{$key}->{$value}\n";
}
}
class CFile implements ICache{
public function set($key,$value)
{
echo "file:{$key}->{$value}\n";
}
}
class Cache{
private $drive;
public function __construct(ICache $drive)
{
$this->drive = $drive;
}
public function set($key,$value){
$this->drive->set($key,$value);
}
}
$cache = new Cache(new CFile());
$cache->set("name","sunny");
登入後複製
很多人看到這段程式碼的時候想著,那我在構造方法直接把要的物件傳進去不就好了嗎?為什麼還要定義一個interface呢?其實定義interface是為了規範程式碼,不管你使用哪個驅動,只要實現了我這個interface的都可以用,沒有interface開發者在開發驅動的時候就會不知道這個驅動裡面該有什麼方法。當我們使用interface之後大家只要面向介面程式設計,Cache完全不管類是怎麼實現的,Cache只是根據interface的方法進行操作。
一句話總結:依賴倒置實現鬆耦合
<?php
class Container
{
// 當前容器物件
private static $instance;
// 存放在容器裡面到範例
protected $instances = [];
private function __construct()
{
}
public static function getInstance()
{
if (!self::$instance) {
self::$instance = new static();
}
return self::$instance;
}
/**
* 獲取物件範例
* @param $key
* @return mixed
*/
public function get($key)
{
if (isset($this->instances[$key])) {
return $this->instances[$key];
}
}
/**
* 繫結物件、閉包、類到容器
* @param $key
* @param null $concrete
* @return Container
*/
public function bind($key, $concrete = null)
{
if ($concrete instanceof Closure) {
$this->instances[$key] = $concrete;
} elseif (is_object($concrete)) {
$this->instances[$key] = $concrete;
}
return $this;
}
}
class Sunny
{
public function getName()
{
echo time() . "\n";
}
}
$app = Container::getInstance();
$sunny = $app->bind(Sunny::class,new Sunny());
$sunny = $app->get(Sunny::class);
$sunny->getName();
登入後複製
Container.php
<?php
class Container
{
// 當前容器物件
private static $instance;
// 存放在容器裡面到範例
protected $instances = [];
private function __construct()
{
}
public static function getInstance()
{
if (!self::$instance) {
self::$instance = new static();
}
return self::$instance;
}
/**
* 獲取物件範例
* @param $key
* @return mixed
* @throws ReflectionException
*/
public function get($key)
{
if (isset($this->instances[$key])) {
return $this->instances[$key];
}
return $this->make($key);
}
/**
* 繫結物件、閉包、類到容器
* @param $key
* @param null $concrete
* @return Container
* @throws ReflectionException
*/
public function bind($key, $concrete = null)
{
if ($concrete instanceof Closure) {
$this->instances[$key] = $concrete;
} elseif (is_object($concrete)) {
$this->instances[$key] = $concrete;
} else {
$this->make($key, $concrete);
}
return $this;
}
/**
* 建立類繫結到類範例
* @param $abstract
* @param null $atgs
* @return mixed
* @throws ReflectionException
*/
public function make($abstract, $atgs = null)
{
if (isset($this->instances[$abstract])) {
return $this->instances[$abstract];
}
$object = $this->invokeClass($abstract);
$this->instances[$abstract] = $object;
return $object;
}
/**
* 反射解析類
* @param $abstract
* @return object
* @throws ReflectionException
*/
public function invokeClass($abstract)
{
$reflectionClass = new \ReflectionClass($abstract);
// 獲取構造方法
$construct = $reflectionClass->getConstructor();
// 獲取引數得到範例
$params = $construct ? $this->parserParams($construct) : [];
$object = $reflectionClass->newInstanceArgs($params);
return $object;
}
/**
* 解解構造方法引數
* @param $reflect
* @return array
* @throws ReflectionException
*/
public function parserParams(ReflectionMethod $reflect)
{
$args = [];
$params = $reflect->getParameters();
if (!$params) {
return $args;
}
if (count($params) > 0) {
foreach ($params as $param) {
$class = $param->getClass();
if ($class) {
$args[] = $this->make($class->getName());
continue;
}
// 獲取變數的名稱
$name = $param->getName();
// 預設值
$def = null;
// 如果有預設值,從預設值獲取型別
if ($param->isOptional()) {
$def = $param->getDefaultValue();
}
$args[] = $_REQUEST[$name] ?? $def;
}
}
return $args;
}
}
Test.php
<?php
class Test
{
public $name;
private $test1;
public function __construct(Test1 $test1)
{
$this->test1 = $test1;
$this->name = $this->test1->getName();
}
}
Test1.php
<?php
class Test1
{
public function getName(){
return "test1返回的名字";
}
}
Sunny.php
<?php
require_once "./Container.php";
require_once "./Test.php";
require_once "./Test1.php";
class Sunny
{
private $test;
public function __construct(Test $test)
{
$this->test = $test;
}
public function getName()
{
echo "獲取test裡面的name:{$this->test->name}\n";
}
}
$app = Container::getInstance();
$sunny = $app->get(Sunny::class);
$sunny->getName();
登入後複製
推薦學習:《》
以上就是一文理解和實現現代PHP框架裡的IOC容器的詳細內容,更多請關注TW511.COM其它相關文章!