模擬鴨子游戲的需求
SimUDuck
游戲中會出現(xiàn)各種鴨子,一邊游泳戲水,一邊呱呱叫。
通過標(biāo)準(zhǔn)的OO技術(shù),設(shè)計(jì)一個(gè)超類。
需求增加
此程序需要會飛 的鴨子競爭者拋在后頭
方法一:在 Duck 超類加上 fly() 方法
但問題發(fā)生了:并非 Duck 所有的子類都會飛(玩具鴨子)
注意:當(dāng)涉及到維護(hù)是,為了“復(fù)用”(reuse)目的而使用繼承,結(jié)局并不完美
方法二:覆蓋超類的方法
當(dāng)有新的鴨子出現(xiàn),要檢查每個(gè)鴨子可能要覆蓋 fly() 和 quark() ...
- 利用繼承來提供行為,導(dǎo)致的缺點(diǎn)
- 代碼在多個(gè)子類中重復(fù)
- 運(yùn)行時(shí)的行為不容易改變
- 很難知道所有鴨子的全部行為
- 改變引發(fā)全身,造成其他鴨子不想要的改變
方法三:利用接口
可以把 fly() 從超類中提取出來,放進(jìn)一個(gè)“Flyable接口”中。這么一來,只有會飛的鴨子才實(shí)現(xiàn)此接口。
看似不錯(cuò)(只適應(yīng)用不多,且飛的方式不同的情況),但是在會飛的鴨子種類很多后,要稍微修改一下飛行的行為,就是一個(gè)災(zāi)難...
分析
- 不是所有的子類都具有飛行和呱呱叫的行為,所以繼承不合適。
- 雖然 Flyable 和 Quackable 可以解決“一部分”問題,但是卻造成代碼無法復(fù)用,這只是從一個(gè)坑進(jìn)入另一個(gè)坑。
- 甚至,在會飛的鴨子中,飛行動作可能還有多種變化。。。
解決之道--“采用良好的OO軟件設(shè)計(jì)原則”
軟件開發(fā)的一個(gè)不變的真理 --> 需求和改變
第一個(gè)設(shè)計(jì)原則:找出應(yīng)用中可能需要變化之處,把它們獨(dú)立出來,不要和那些不需要變化的代碼混在一起
把會變化的部分取出來并“封裝”起來,好讓其它部分不會受到影響
為了把這兩個(gè)行為從Duck 類中分開,將它們從 Duck 類中取出來,建立一組新類來代表每個(gè)行為。
設(shè)計(jì)鴨子的行為
讓鴨子類中的行為可以動態(tài)的改變就好了。我們應(yīng)該在鴨子類中包含設(shè)定行為的方法,這樣就可以在“運(yùn)行時(shí)”動態(tài)的“改變”飛行行為。
第二個(gè)設(shè)計(jì)原則:針對接口編程,而不是針對實(shí)現(xiàn)編程
針對超類型編程
利用接口代表每個(gè)行為,比方說 FiyBehavior 和 QuackBehavior ,而行為的每個(gè)實(shí)現(xiàn)都將實(shí)現(xiàn)其中的一個(gè)接口。由行為類而不是Duck類來實(shí)現(xiàn)接口。
- 以前的做法:行為來自 Duck 超類的具體實(shí)現(xiàn),或是繼承某個(gè)接口并由子類自行實(shí)現(xiàn)而來。都依賴于“實(shí)現(xiàn)”,被實(shí)現(xiàn)綁的死死的,沒辦法更改行為(除非寫更多代碼)。
vs
- 新設(shè)計(jì)中:鴨子的子類將使用接口(FlyBehavior 與 QuackBehavior)所標(biāo)示的行為,實(shí)際的“實(shí)現(xiàn)”不會被綁死在鴨子的子類中。即特定的具體行為編寫在實(shí)現(xiàn)了FlyBehavior 與 QuackBehavior 的子類中。
這樣的設(shè)計(jì),可以放飛行和呱呱叫的動作被其他的對象復(fù)用,因?yàn)檫@些行為已經(jīng)與鴨子類無關(guān)了。
而我們可以新增一些行為,不會影響到既有的行為類,也不會影響“使用”到飛行行為的鴨子類。
整合鴨子的行為
關(guān)鍵在于:鴨子現(xiàn)在會將飛行和呱呱叫的動作“委托”(delegate)別人處理,而不是使用定義在 Duck 類(或子類)內(nèi)的呱呱叫和飛行方法。
做法:
- 首先,在 Duck 類中加入“兩個(gè)實(shí)例變量”,分別為“flyBehavior”與“quackBehavior”,聲明為接口類型(而不是具體類實(shí)現(xiàn)類型),每個(gè)鴨子的對象都會動態(tài)地設(shè)置這些變量以在運(yùn)行時(shí)引用正確的行為類型。
- 實(shí)現(xiàn) performQuack();
public class Duck{
QuackBehavior quackBehavior;
// 還有更多
public void performQuack(){
quackBehavior.quack();//鴨子對象不親自處理呱呱叫行為,而是委托給quackBehavior引用的對象。
}
}
- 如何設(shè)定 flyBehavior 與 quackBehavior的實(shí)例變量
public class MallardDuck extends Duck{
public MallardDuck(){
quackBehavior = new Quack();
flyBehavior = new FlyWithWings();
}
//別忘了,因?yàn)?MallarDuck 繼承了 Duck 類,所以具有 flyBehavior 與 quackBehavior 實(shí)例變量
}
我們正在構(gòu)造器里面制造了具體的Quack實(shí)現(xiàn)類的實(shí)例(在后面的模式中,可以修正這一點(diǎn))
動態(tài)設(shè)定行為
通過 set 方法來設(shè)定鴨子的行為,而不是在鴨子的構(gòu)造器內(nèi)實(shí)例化。
Duck 類中 加入 setFlyBehavior(FlyBehavior fly);