接着我的上一篇文章策略模式學習1。
讓我們來回顧一下上面設計中橡皮鴨類的代碼,並提出一下問題。
/**
* 橡皮鴨
* @author Administrator
*
*/
public class RubberDuck extends Duck {
@Override
void display() {
System.out.println("橡皮鴨...");
}
/*
* 橡皮鴨子不會呱呱叫
* 但是問題來了,這個橡皮鴨子會飛?
* 那設計人員會說:一樣覆蓋掉就好
*/
@Override
public void quack() {
//覆蓋超類的呱呱叫
System.out.println("吱吱叫...");
}
@Override
void fly() {
//覆蓋,變成什麼事兒都不做
;
}
/*
* 那麼問題來了,以後每個月更新,常常會出現新的鴨子子類,這要被迫每次檢查
* 並可能覆蓋quack() 和 fly() 這簡直就是無窮無盡的噩夢!!!!
* 這樣設計的缺點:
* 1.代碼在多個子類中重複
* 2.運行時的行爲不容易被改變
* 3.改變會牽一髮動全身,造成其他鴨子不想要的改變
* 4.很難知道鴨子的全部行爲,因爲有了不屬於它本身的行爲。
*/
//重複代碼變多,那麼你認爲覆蓋幾個方法就算是差勁,那麼對於48個Duck子類都要稍微修改一下
//飛行的行爲,你又怎麼說?需求:會飛的鴨子中,飛的動作可能有多種變化.
}
因此我們可以看到,利用繼承來提供Duck的行爲,會導致以上所述的問題以及缺點。
那利用接口如何?將飛行方法和呱呱叫的方法做成一個接口,分別讓具備這兩個行爲的鴨子實現這個接口,就能夠解決不再會有會飛的鴨子。
但是缺點是:造成了代碼無法複用(雖然鴨子類中實現了飛行的方法,
但是這個鴨子在某個場景下又想要其他類型的飛行方法呢?這有點類似於遊戲中的升級,解鎖了某技能或在某個副本中,會以新的行爲出現)。這又跳進了另外一個坑。
另外,如果飛行的動作可能還有多種變化呢?比如火箭式飛行,螺旋式飛行等等。
軟件開發的一個不變的真理:改變(change).不管軟件設計得多好,一段時間之後,總是需要成長和改變,否則軟件就會“死亡”。
利用Flyable和Quackable一開始似乎還不錯,解決了只有會飛的鴨子才實現Flyable,但是java接口不具有實現代碼,所以實現接口無法達到代碼的複用。這就意味着無論何時你需要修改某個行爲,你必須得往下追蹤到並在每一個定義此行爲的類中修改它,一不小心,可能會造成新的錯誤。
幸運的是,這裏有一個設計原則:
找出應用中可能需要變化之處,把它們獨立出來,不要和那些不需要變化的代碼混在一起。換句話說:如果每次新的需求一來,都會使某方面的代碼發生變化,那麼你就可以確定,這部分的代碼需要被抽出來,和其他穩定的代碼有所區分。對這個原則的另外一種思考方式:把會變化的部分取出並封裝出來,以便以後可以輕易地改動或擴充此部分,而不影響不需要變化的其他部分。
我們知道Duck類的fly()和quack()會隨着鴨子的不同而改變。因此我們把這兩個行爲從Duck類中分離出來,建立一組新類來代表每個行爲。
如何設計鴨子的行爲?我們想要產生一個新的綠頭鴨實例,並指定特定的“類型”的飛行行爲給它。那麼幹脆順便讓鴨子的行爲可以動態的改變好了。換句話說:我們應該在鴨子類中包含設定行爲的方法,這樣就可以在“運行時”動態地“改變”綠頭鴨的飛行行爲。
設計原則:針對接口編程,而不是針對實現編程。
public abstract class Duck {
FlyBehavior flyBehavior;
QuackBehavior quackBehavior;
// 行爲變量被聲明爲行爲 "接口" 類型
public void swim() {
System.out.println("游來游去...");
}
abstract void display();
//鴨子對象不親自處理呱呱叫行爲,而是委託給quackBehavior引用的對象去執行
void performQuack() {
quackBehavior.quack();
}
void performFly() {
flyBehavior.fly();
}
public FlyBehavior getFlyBehavior() {
return flyBehavior;
}
public void setFlyBehavior(FlyBehavior flyBehavior) {
this.flyBehavior = flyBehavior;
}
public QuackBehavior getQuackBehavior() {
return quackBehavior;
}
public void setQuackBehavior(QuackBehavior quackBehavior) {
this.quackBehavior = quackBehavior;
}
/*public void quack() {
System.out.println("呱呱叫...");
}
void fly(){
System.out.println("飛來飛去...");
}*/
//鴨子的其他方法
}
public interface FlyBehavior {
void fly();
}
public class FlyWithWings implements FlyBehavior {
@Override
public void fly() {
System.out.println("這裏實現了所有有翅膀的鴨子飛行動作...");
}
}
public class FlyNoWay implements FlyBehavior {
@Override
public void fly() {
//什麼都不做,不會飛
System.out.println("I can't fly!");
}
}
public class FlyByRocky implements FlyBehavior {
@Override
public void fly() {
// 新增了一個火箭式的飛行
System.out.println("火箭式的飛行...");
}
}
public interface QuackBehavior {
void quack();
}
public class Quack implements QuackBehavior {
@Override
public void quack() {
// 實現鴨子呱呱叫
System.out.println("呱呱叫...");
}
}
public class Squeak implements QuackBehavior {
@Override
public void quack() {
// 橡皮鴨子吱吱叫
System.out.println("吱吱叫...");
}
}
public class MallardDuck extends Duck {
@Override
void display() {
System.out.println("綠頭鴨..");
}
}
public class ModelDuck extends Duck {
public ModelDuck(){
this.flyBehavior = new FlyNoWay();
this.quackBehavior = new MuteQuack();
}
@Override
void display() {
// 這是一隻模型鴨
System.out.println("模型鴨...");
}
}
// 新增一個鴨鳴器,用來捕獲其他真正鴨子
public class DuckCall implements QuackBehavior{
QuackBehavior quack;
public QuackBehavior getQuack() {
return quack;
}
public void setQuack(QuackBehavior quack) {
this.quack = quack;
}
@Override
public void quack() {
quack.quack();
}
}
//測試類
public class MiniDuckSimulator {
public static void main(String[] args) {
/*Duck mallard = new MallardDuck();
mallard.setFlyBehavior(new FlyWithWings());
mallard.setQuackBehavior(new Quack());
mallard.performFly();
mallard.performQuack();*/
/*Duck modelDuck = new ModelDuck();
modelDuck.performFly();
modelDuck.setFlyBehavior(new FlyByRocky());//模型鴨可以動態地改變飛行行爲
modelDuck.setQuackBehavior(new MuteQuack());
modelDuck.performFly();*/
//實現一個鴨鳴器用來誘捕野鴨
DuckCall duckCall = new DuckCall();
duckCall.setQuack(new Quack());
duckCall.quack();//利用多態,可以動態的模擬各種叫聲
}
}
public class MuteQuack implements QuackBehavior {
@Override
public void quack() {
// 什麼都不做,不會叫
;
}
/*
* 這樣設計,可以讓飛行和呱呱叫的動作被其他的對象複用,因爲這樣行爲已經與鴨子無關了
* 而我們可以新增一些行爲,不會影響到既有的行爲類,不會影響“使用”到飛行行爲的鴨子類了。
* 有了繼承的“複用”的好處,卻沒有了繼承所帶來的包袱
*/
}
這裏也用到另外一個設計原則:多用組合,少用繼承。
“有一個”可能比“是一個”更好。有一個關係相當有趣,每個鴨子都有一個FlyBehavior和QuackBehavior,當你將兩個類結合起來使用,如通本例一般,這就是用到了組合。