你要设计一个方法,要能通过floppy bird的关卡
一种鸟只能飞低、超低、高、超高
假设你写了一个这样只能飞高的鸟的class
class FlyHighBird
{
public function fly()
{
return "fly high";
}
}
但是floppy bird的关卡的class中
Class Stage
{
private $bird;
private $interspace;
function __construct()
{
$this->bird = new FlyHighBird();
}
function check()
{
if($this->bird->fly() == $interspace)
return true;
return false;
}
// other method dynamically change $interspace
}
Stage在编译时期就决定依赖于Bird物件
那么你这只只能飞高的鸟,势必过不了关卡现在空隙位置是
低、超低、或超高的检查
那么要能一直通过不同高度的关卡检查,
你势必要在runtime改变Stage依赖的物件才行
其实Stage根本不care你是哪种鸟,甚至根本不care你是不是鸟,
他只在乎你是飞的高还是飞的低,也就是只在乎你的
"飞的行为",你手边有四种鸟都只能飞不同的高度,那么你只要能动态改变Stage依赖
的$bird物件中的实作,似乎就能通过每一次的检查
所以你把"飞的行为"从四种鸟的class中一般化成接口
interface FlyBehavior
{
public function fly();
}
四种鸟分别实作这个接口完成四种不同高度的飞行行为
改变Stage相依的物件
class Stage
{
private $flyBehavior;
private $interspace;
function setFlyBehavior($flyBehavior)
{
$this->flyBehavior = $flyBehavior;
}
function check()
{
if($this->flyBehavior->fly() == $interspace)
return true;
return false;
}
//other methods
}
现在Stage 没有在编译时期依赖于某一种鸟类了,而是透过FlyBehavior接口
从Stage的外部注入其中一种鸟类的实作,来动态透过setFlyBehavior的替换目前
依赖的实作,这种依赖关系从原本自己物件内部,到被抽到外部决定再透过setter
或建构子注入就是依赖注入,能做到runtime才根据一些参数
(比如说Stage有个方法getInterspace()提供给你$interspace的数值,再在外部加以判断)
决定要注入哪一种实作,来达到通过每一次检查的弹性
将来floppy bird改版了,要检查是否到达"宇宙的高度",才算通过
你只要打造一个火箭的Class,一样完成FlyBehavior的实作,让他飞到宇宙的高度
再注入到Stage物件就能通过关卡了,而你"并没有修改Stage一开始相依物件的程式码"
因为Stage原本直接依赖于某种鸟类的实作,这样的关系被"接口"decoupling了
也就是"针对接口写程式,不要针对实作写程式"的OO守则
配合DI的方式能做到更多面对需求变更时,还能保有弹性,你要修改的程式少了很多