1. 程式人生 > >php依賴註入

php依賴註入

php 依賴註入

在軟件工程領域,依賴註入(Dependency Injection)是用於實現控制反轉(Inversion of Control)的最常見的方式之一。本文主要介紹依賴註入原理和常見的實現方式,重點在於介紹這種年輕的設計模式的適用場景及優勢。


首先我們來一個實例,上代碼

<?php

class A
{
	public function test()
	{
		echo 'this is A!<br>';
		$b = new B();
		$b->test();
	}
}

class B
{
	public function test()
	{
		echo 'this is B!<br>';
		$c = new C();
		$c->test();
	}
}

class C
{
	public function test()
	{
		echo 'this is C!<br>';
	}
}

$obj = new A();

$obj->test();


結果是:

this is A!
this is B!
this is C!


從代碼分析,A類依賴B類,B類依賴C類。這是我們最原始的實現思路.這種實現思路很明顯會有問題

假如我們現在B類修改下,代碼如下:

class B
{

	public $name;

	public function __construct($name)
	{
		$this->name = $name;
	}

	public function test()
	{
		echo 'this is B'.$this->name.'!<br>';
		$c = new C();
		$c->test();
	}
}


此時再看我們原來A類test方法直接調用明顯會有問題,於是此時我們需要將原來代碼:

class A
{
	public function test()
	{
		echo 'this is A!<br>';
		$b = new B();
		$b->test();
	}
}

修改成:

class A
{
	public function test()
	{
		echo 'this is A!<br>';
		$b = new B('(class B)');//構造的時候多加了一個參數
		$b->test();
	}
}

如果此時C類構造方法也變動了呢,B的方法裏面也同樣受影響,很明顯可用性非常低


為了解耦,此時我們用到第二種思路:構造器註入,直接上代碼:

<?php

class A
{
	public $obj;

	public function __construct(B $b)
	{
		$this->obj = $b;
	}

	public function test()
	{
		echo 'this is A!<br>';
		$this->obj->test();
	}
}

class B
{
	public $obj;
	public function __construct(C $c)
	{
		$this->obj = $c;
	}

	public function test()
	{
		echo 'this is B!<br>';
		$this->obj->test();
	}
}

class C
{
	public function test()
	{
		echo 'this is C!<br>';
	}
}
$c = new C();
$b = new B($c);
$obj = new A($b);
$obj->test();

這種方法可以解決第一種方法,如果依賴的類構造方法(比如B類)有所改動,A類不需要改動任何代碼。但是久而久之,這種方法毛病也出來了,我們首先必須要先實例化C類,再實例化B類,最後再實例化A類。


了解到第二種方案需要優化,於是出現了基本成型的第三種方案,此時我們用到了container(容器)和instance(實例),直接上代碼:

class Container
{

	public static $_classes = [];

	public static $_definations = [];
	
	
	public function get($class)
	{
		if(isset(self::$_classes[$class]))
			return self::$_classes[$class];

		//當前類所依賴的類
		$depends = [];
		
		$tc = new ReflectionClass($class);
		//得到構造方法
		$constructor = $tc->getConstructor();

		//得到構造方法的參數
		if($constructor !== NULL)
		{
			foreach($constructor->getParameters() as $parameter)
			{
				if($parameter->isDefaultValueAvailable())
				{
					$depends[] = $parameter->getDefaultValue();
				}
				else
				{
					$pc = $parameter->getClass();
					$instance = Instance::getInstance($pc == NULL ? NULL : $pc->getName());
					$depends[] = $instance;
				}
			}
		}

		foreach($depends as $k => $v)
		{
			if($v instanceof Instance)
			{
				if($v->id !== NULL)
				{
					$depends[$k] = $this->get($v->id);
				}
			}
		}

		$tm_instance = $tc->newInstanceArgs($depends);
		self::$_classes[$class] = $tm_instance;
		return $tm_instance;
	}
}

class Instance{
	/**
	 * @var 類唯一標示
	 */
	public $id;

	/**
	 * 構造函數
	 * @param string $id 類唯一ID
	 * @return void
	 */
	public function __construct($id)
	{
		$this->id = $id;
	}

	/**
	 * 獲取類的實例
	 * @param string $id 類唯一ID
	 * @return Object Instance
	 */
	public static function getInstance($id)
	{
		return new self($id);
	}
}

class Base{
	/**
	 * 魔術方法
	 * @param string $name
	 * @param string $value
	 * @return void
	 */
	public function __set($name, $value)
	{
		$this->{$name} = $value;
	}
}

class A extends Base{
	private $instanceB;

	public function __construct(B $instanceB)
	{
		$this->instanceB = $instanceB;
	}

	public function test()
	{
		echo 'this is A!<br/>';
		$this->instanceB->test();
	}
}


class B  extends Base{
	private $instanceC;

	public function __construct(C $instanceC)
	{
		$this->instanceC = $instanceC;
	}

	public function test()
	{
		echo 'this is B!<br/>';
		return $this->instanceC->test();
	}
}

class C  extends Base{
	public function test()
	{
		echo 'this is C!';
	}
}


$container = new Container();

$obj_a = $container->get('A');

$obj_a->test();

此方法有參考yii2中yii2\di\container實現,只是將它簡化了,更容易看懂。重點看看container的get方法


php依賴註入