接著我的上一篇文章策略模式學習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,當你將兩個類結合起來使用,如通本例一般,這就是用到了組合。
新聞熱點
疑難解答