這裡先假設一個場景:
有一個工廠(Factory),需要工人(Worker) 使用某個機器(Machine)來生產某種產品
即有這樣的依賴關係: Factory –> Worker –> Machine
不使用注入
程式碼大概是這樣子:
class Machine{
function run(){
echo `機器開動`;
}
}
class Worker {
function work(){
echo "工人開動機器 -> ";
$machine = new Machine();
$machine -> run();
}
}
class Factory{
function produce(){
echo "工廠叫工人開工 -> ";
$worker = new Worker();
$worker -> work();
}
}
$factory = new Factory();
$factory -> produce();
可以看出來,這裡所依賴的物件都由類自己在內部例項化,是一種強耦合,不利於測試和維護。比如我現在要改成另一種工人來生產,那就要改工廠內部,這是不合理的。
手工注入
所謂的注入,就是將類所依賴的物件的例項化操作放在類外面,同時使用Interface來作出約束:
Interface Machine{
public function run();
}
Interface Worker{
public function work();
}
class Machine1 implements Machine{
function run(){
echo `機器 1 開動`;
}
}
class Machine2 implements Machine{
function run(){
echo `機器 2 開動`;
}
}
class Worker1 implements Worker{
private $machine;
public function __construct(Machine $machine){
$this -> machine = $machine;
}
function work(){
echo "工人 1 開動機器 -> ";
$this -> machine -> run();
}
}
class Worker2 implements Worker{
private $machine;
public function __construct(Machine $machine){
$this -> machine = $machine;
}
function work(){
echo "工人 2 開動機器 -> ";
$this -> machine -> run();
}
}
class Factory{
private $worker;
public function __construct(Worker $worker){
$this -> worker = $worker;
}
function produce(){
echo "工廠叫工人開工 -> ";
$this -> worker -> work();
}
}
$machine = new Machine1();
$worker = new Worker2($machine);
$factory = new Factory($worker);
$factory -> produce();
可以看出來,這樣的好處是解耦。比如:可以由工人1開動機器2來生產,也可以由工人2來開動機器1來生產,以後也可以隨時用新的工人(只要他會work)、Worker也可以隨時換其它的機器(只要它會run)來生產。這種轉換都不需要修改工廠或工人的程式碼。
那麼問題來了,現在只是把例項化從裡面移到了外面,但如果依賴的東西多了,也是很麻煩的,這就需要一個自動注入的機制,也就是平時經常聽到的注入容器,常見的容器都是用到反射來實現,而且要在建構函式中宣告注入的型別,相對還是比較麻煩。
在本篇,我嘗試用另一種方式實現。
trait自動注入
trait可以簡單理解為可以複用的方法,下面來看看怎麼用trait來實現自動注入。
思路就是用trait來實現魔術方法__get,通過該方法來自動生成依賴的物件,先看完整程式碼
trait DI{
private $map = [`Worker` => `Worker1`,`Machine`=>`Machine2`];
function __get($k){
if(preg_match(`/^[A-Z]/`, $k)) {
$obj = new $this -> map[$k];
if($obj instanceof $k){
return $obj;
}else{
exit("不符約定");
}
}
}
}
Interface Machine{
public function run();
}
Interface Worker{
public function work();
}
class Machine1 implements Machine{
function run(){
echo `機器 1 開動`;
}
}
class Machine2 implements Machine{
function run(){
echo `機器 2 開動`;
}
}
class Worker1 implements Worker{
use DI;
function work(){
echo "工人 1 開動機器 -> ";
$this -> Machine -> run();
}
}
class Worker2 implements Worker{
use DI;
function work(){
echo "工人 2 開動機器 -> ";
$this -> Machine -> run();
}
}
class Factory{
use DI;
function produce(){
echo "工廠叫工人開工 -> ";
$this -> Worker -> work();
}
}
$factory = new Factory();
$factory -> produce();
- trait中的map用來演示實現類和介面的繫結關係,以便進行型別約束,實際應用時可以用配置或其它方式實現.
- 類中要使用依賴注入時,需宣告use DI, 同時,注入的物件為首字母大寫(你也可以用其它規則,相應的修改trait中的判斷)
當然了,這只是一個很粗糙的演示,只實現了基本的自動注入,它還有很多問題,比如原來的類如果也有__get方法時,就會產生覆蓋。
有興趣的可以嘗試完善一下看能不能在專案中實際使用。