xhchc

          危波帆墻,笑談只在桃花上;與誰共尚,風(fēng)吹萬里浪; 相依相偎,不做黃泉想;莫惆悵,碧波潮生,一蕭自狂放……

           

          設(shè)計模式解讀之一: 策略模式(轉(zhuǎn))

          原文出自:http://www.javaeye.com/topic/328262

          當(dāng)我們掌握了Java的語法,當(dāng)我們了解了面向?qū)ο蟮姆庋b、繼承、多態(tài)等特性,當(dāng)我們可以用Swing、Servlet、JSP技術(shù)構(gòu)建桌面以及Web應(yīng)用,不意味著我們可以寫出面向?qū)ο蟮某绦颍灰馕吨覀兛梢院芎玫膶崿F(xiàn)代碼復(fù)用,彈性維護(hù),不意味著我們可以實現(xiàn)在維護(hù)、擴展基礎(chǔ)上的代碼復(fù)用。一把刀,可以使你制敵于無形而于江湖揚名,也可以只是一把利刃而使你切菜平靜。Java,就是這把刀,它的威力取決于你使用的方式。當(dāng)我們陷入無盡無止重復(fù)代碼的泥沼,當(dāng)我們面臨牽一發(fā)而動全身的維護(hù)惡夢, 你應(yīng)該想起“設(shè)計模式”這個行動秘笈。面向?qū)ο蟮木x,看似平淡,其實要經(jīng)過艱苦實踐才能成功。而構(gòu)造OO系統(tǒng)的隱含經(jīng)驗于是被前人搜集而成并冠以“設(shè)計模式”之名。我們應(yīng)該在編碼行動初始就攜帶以它。接下來,讓我們步“四人組”先行者之后,用中國文字、用實際案例領(lǐng)略模式于我們代碼煥然一新的改變:

           

          設(shè)計模式解讀之一: 策略模式

           

              1. 模式定義
             
                  把會變化的內(nèi)容取出并封裝起來,以便以后可以輕易地改動或擴充部分,而不影響不需要變化的其他部分;

              2. 問題緣起

              當(dāng)涉及至代碼維護(hù)時,為了復(fù)用目的而使用繼承,結(jié)局并不完美。對父類的修改,會影響到子類型。在超類中增加的方法,會導(dǎo)致子類型有該方法,甚至連那些不該具備該方法的子類型也無法免除。示例,一個鴨子類型:

              public abstract class Duck {
                  //所有的鴨子均會叫以及游泳,所以父類中處理這部分代碼
                  public void quack() {
                      System.out.println("Quack");
                  }
                 
                  public void swim() {
                      System.out.println("All ducks float, even decoys.");       
                  }
                 
                  //因為每種鴨子的外觀是不同的,所以父類中該方法是抽象的,由子類型自己完成。
                  public abstract void display();
              }

              public class MallardDuck extends Duck {
                  //野鴨外觀顯示為綠頭
                  public void display() {
                      System.out.println("Green head.");
                  }
              }

              public class RedHeadDuck extends Duck {
                  //紅頭鴨顯示為紅頭
                  public void display() {
                      System.out.println("Red head.");
                  }
              }

              public class RubberDuck extends Duck {
                  //橡皮鴨叫聲為吱吱叫,所以重寫父類以改寫行為
                  public void quack() {
                      System.out.println("Squeak");
                  }

                  //橡皮鴨顯示為黃頭
                  public void display() {
                      System.out.println("Yellow head.");
                  }
              }

              上述代碼,初始實現(xiàn)得非常好。現(xiàn)在我們?nèi)绻oDuck.java中加入fly()方法的話,那么在子類型中均有了該方法,于是我們看到了 會飛的橡皮鴨子,你看過嗎?當(dāng)然,我們可以在子類中通過空實現(xiàn)重寫該方法以解決該方法對于子類型的影響。但是父類中再增加其它的方法呢?

              通過繼承在父類中提供行為,會導(dǎo)致以下缺點:

              a. 代碼在多個子類中重復(fù);
              b. 運行時的行為不容易改變;
              c. 改變會牽一發(fā)動全身,造成部分子類型不想要的改變;

              好啦,還是剛才鴨子的例子,你也許想到使用接口,將飛的行為、叫的行為定義為接口,然后讓Duck的各種子類型實現(xiàn)這些接口。這時侯代碼類似于:

              public abstract class Duck {
                  //將變化的行為 fly() 以及quake()從Duck類中分離出去定義形成接口,有需求的子類中自行去實現(xiàn)

                  public void swim() {
                      System.out.println("All ducks float, even decoys.");       
                  }
                 
                  public abstract void display();
              }

              //變化的 fly() 行為定義形成的接口
              public interface FlyBehavior {
                  void fly();
              }

              //變化的 quack() 行為定義形成的接口
              public interface QuackBehavior {
                  void quack();
              }

              //野鴨子會飛以及叫,所以實現(xiàn)接口  FlyBehavior, QuackBehavior
              public class MallardDuck extends Duck implements FlyBehavior, QuackBehavior{
                  public void display() {
                      System.out.println("Green head.");
                  }

                  public void fly() {
                      System.out.println("Fly.");               
                  }

                  public void quack() {
                      System.out.println("Quack.");               
                  }
              }

              //紅頭鴨子會飛以及叫,所以也實現(xiàn)接口  FlyBehavior, QuackBehavior
              public class RedHeadDuck extends Duck implements FlyBehavior, QuackBehavior{
                  public void display() {
                      System.out.println("Red head.");
                  }   

                  public void fly() {
                      System.out.println("Fly.");               
                  }

                  public void quack() {
                      System.out.println("Quack.");               
                  }   
              }

              //橡皮鴨不會飛,但會吱吱叫,所以只實現(xiàn)接口QuackBehavior
              public class RubberDuck extends Duck implements QuackBehavior{
                  //橡皮鴨叫聲為吱吱叫
                  public void quack() {
                      System.out.println("Squeak");
                  }

                  //橡皮鴨顯示為黃頭
                  public void display() {
                      System.out.println("Yellow head.");
                  }
              }

              上述代碼雖然解決了一部分問題,讓子類型可以有選擇地提供一些行為(例如 fly() 方法將不會出現(xiàn)在橡皮鴨中).但我們也看到,野鴨子MallardDuck.java和紅頭鴨子RedHeadDuck.java的一些相同行為代碼不能得到重復(fù)使用。很大程度上這是從一個火坑跳到另一個火坑。

              在一段程序之后,讓我們從細(xì)節(jié)中跳出來,關(guān)注一些共性問題。不管使用什么語言,構(gòu)建什么應(yīng)用,在軟件開發(fā)上,一直伴隨著的不變的真理是:需要一直在變化。不管當(dāng)初軟件設(shè)計得多好,一段時間之后,總是需要成長與改變,否則軟件就會死亡。

              我們知道,繼承在某種程度上可以實現(xiàn)代碼重用,但是父類(例如鴨子類Duck)的行為在子類型中是不斷變化的,讓所有子類型都有這些行為是不恰當(dāng)?shù)摹N覀兛梢詫⑦@些行為定義為接口,讓Duck的各種子類型去實現(xiàn),但接口不具有實現(xiàn)代碼,所以實現(xiàn)接口無法達(dá)到代碼復(fù)用。這意味著,當(dāng)我們需要修改某個行為,必須往下追蹤并在每一個定義此行為的類中修改它,一不小心,會造成新的錯誤。

              設(shè)計原則:把應(yīng)用中變化的地方獨立出來,不要和那些不需要變化的代碼混在一起。這樣代碼變化引起的不經(jīng)意后果變少,系統(tǒng)變得更有彈性。

              按照上述設(shè)計原則,我們重新審視之前的Duck代碼。

              1) 分開變化的內(nèi)容和不變的內(nèi)容

                 Duck類中的行為 fly(), quack(), 每個子類型可能有自己特有的表現(xiàn),這就是所謂的變化的內(nèi)容。
                     Duck類中的行為 swim() 每個子類型的表現(xiàn)均相同,這就是所謂不變的內(nèi)容。

                 我們將變化的內(nèi)容從Duck()類中剝離出來單獨定義形成接口以及一系列的實現(xiàn)類型。將變化的內(nèi)容定義形成接口可實現(xiàn)變化內(nèi)容和不變內(nèi)容的剝離。其實現(xiàn)類型可實現(xiàn)變化內(nèi)容的重用。這些實現(xiàn)類并非Duck.java的子類型,而是專門的一組實現(xiàn)類,稱之為"行為類"。由行為類而不是Duck.java的子類型來實現(xiàn)接口。這樣,才能保證變化的行為獨立于不變的內(nèi)容。于是我們有:

                 變化的內(nèi)容:

                 //變化的 fly() 行為定義形成的接口
                 public interface FlyBehavior {
                  void fly();
                 }
                  
                 //變化的 fly() 行為的實現(xiàn)類之一
                 public class FlyWithWings implements FlyBehavior {
                  public void fly() {
                      System.out.println("I'm flying.");
                  }
                 }

                 //變化的 fly() 行為的實現(xiàn)類之二
                 public class FlyNoWay implements FlyBehavior {
                  public void fly() {
                      System.out.println("I can't fly.");
                  }
                 }

                     -----------------------------------------------------------------

                 //變化的 quack() 行為定義形成的接口
                 public interface QuackBehavior {
                  void quack();
                 }

                 //變化的 quack() 行為實現(xiàn)類之一
                 public class Quack implements QuackBehavior {
                  public void quack() {
                      System.out.println("Quack");
                  }
                 }

                 //變化的 quack() 行為實現(xiàn)類之二
                 public class Squeak implements QuackBehavior {
                  public void quack() {
                      System.out.println("Squeak.");
                  }
                 }

                 //變化的 quack() 行為實現(xiàn)類之三
                 public class MuteQuack implements QuackBehavior {
                  public void quack() {
                      System.out.println("<< Slience >>");
                  }
                 }

                 通過以上設(shè)計,fly()行為以及quack()行為已經(jīng)和Duck.java沒有什么關(guān)系,可以充分得到復(fù)用。而且我們很容易增加新的行為, 既不影響現(xiàn)有的行為,也不影響Duck.java。但是,大家可能有個疑問,就是在面向?qū)ο笾行袨椴皇求w現(xiàn)為方法嗎?為什么現(xiàn)在被定義形成類(例如Squeak.java)?在OO中,類代表的"東西"一般是既有狀態(tài)(實例變量)又有方法。只是在本例中碰巧"東西"是個行為。既使是行為,也有屬性及方法,例如飛行行為,也需要一些屬性記錄飛行的狀態(tài),如飛行高度、速度等。

              2) 整合變化的內(nèi)容和不變的內(nèi)容

                 Duck.java將 fly()以及quack()的行為委拖給行為類處理。

                 不變的內(nèi)容:

                 public abstract class Duck {
                      //將行為類聲明為接口類型,降低對行為實現(xiàn)類型的依賴
                  FlyBehavior flyBehavior;
                  QuackBehavior quackBehavior;

                  public void performFly() {
                      //不自行處理fly()行為,而是委拖給引用flyBehavior所指向的行為對象
                      flyBehavior.fly();
                  }

                  public void performQuack() {
                      quackBehavior.quack();
                  }

                  public void swim() {
                      System.out.println("All ducks float, even decoys.");       
                  }
                 
                  public abstract void display();
                 }

                 Duck.java不關(guān)心如何進(jìn)行 fly()以及quack(), 這些細(xì)節(jié)交由具體的行為類完成。
                
                 public class MallardDuck extends Duck{
                  public MallardDuck() {
                      flyBehavior=new FlyWithWings();
                      quackBehavior=new Quack();       
                  }
                 
                  public void display() {
                      System.out.println("Green head.");
                  }
                 }

                     測試類:

                 public class DuckTest {
                  public static void main(String[] args) {
                      Duck duck=new MallardDuck();
                      duck.performFly();
                      duck.performQuack();       
                  }
                 }

                 在Duck.java子類型MallardDuck.java的構(gòu)造方法中,直接實例化行為類型,在編譯的時侯便指定具體行為類型。當(dāng)然,我們可以:
                
                 1) 我們可以通過工廠模式或其它模式進(jìn)一步解藕(可參考后續(xù)模式講解);
                 2) 或做到在運行時動態(tài)地改變行為。

              3) 動態(tài)設(shè)定行為

                 在父類Duck.java中增加設(shè)定行為類型的setter方法,接受行為類型對象的參數(shù)傳入。為了降藕,行為參數(shù)被聲明為接口類型。這樣,既便在運行時,也可以通過調(diào)用這二個方法以改變行為。

                 public abstract class Duck {
                  //在剛才Duck.java中加入以下二個方法。
                  public void setFlyBehavior(FlyBehavior flyBehavior) {
                      this.flyBehavior=flyBehavior;
                  }
                 
                  public void setQuackBehavior(QuackBehavior quackBehavior) {
                      this.quackBehavior=quackBehavior;
                  }

                  //其它方法同,省略...
                 }

                     測試類:

                 public class DuckTest {
                  public static void main(String[] args) {
                      Duck duck=new MallardDuck();
                      duck.performFly();
                      duck.performQuack();
                      duck.setFlyBehavior(new FlyNoWay());
                      duck.performFly();
                  }
                 }

                 如果,我們要加上火箭助力的飛行行為,只需再新建FlyBehavior.java接口的實現(xiàn)類型。而子類型可通過調(diào)用setQuackBehavior(...)方法動態(tài)改變。至此,在Duck.java增加新的行為給我們代碼所帶來的困繞已不復(fù)存在。

              該是總結(jié)的時侯了,讓我們從代碼的水中浮出來,做一只在水面上自由游動的鴨子吧:

              3.  解決方案

                  MallardDuck 繼承  Duck抽象類;          -> 不變的內(nèi)容
                  FlyWithWings 實現(xiàn) FlyBehavior接口;     -> 變化的內(nèi)容,行為或算法
              在Duck.java提供setter方法以裝配關(guān)系;    -> 動態(tài)設(shè)定行為

              以上就是策略模式的實現(xiàn)三步曲。接下來,讓我們透過步驟看本質(zhì):
             
              1) 初始,我們通過繼承實現(xiàn)行為的重用,導(dǎo)致了代碼的維護(hù)問題。          -> 繼承, is a
              2) 接著,我們將行為剝離成單獨的類型并聲明為不變內(nèi)容的實例變量并通過  -> 組合, has a
                 setter方法以裝配關(guān)系;

                  繼承,可以實現(xiàn)靜態(tài)代碼的復(fù)用;組合,可以實現(xiàn)代碼的彈性維護(hù);使用組合代替繼承,可以使代碼更好地適應(yīng)軟件開發(fā)完后的需求變化。

              策略模式的本質(zhì):少用繼承,多用組合

          posted on 2009-03-04 17:27 chu 閱讀(210) 評論(0)  編輯  收藏


          只有注冊用戶登錄后才能發(fā)表評論。


          網(wǎng)站導(dǎo)航:
           

          導(dǎo)航

          統(tǒng)計

          常用鏈接

          留言簿(2)

          隨筆檔案

          我的鏈接

          搜索

          最新評論

          閱讀排行榜

          評論排行榜

          主站蜘蛛池模板: 天柱县| 调兵山市| 新昌县| 商都县| 民权县| 东乡| 洪雅县| 城市| 济南市| 托克逊县| 永年县| 油尖旺区| 依兰县| 五常市| 湟中县| 贵州省| 景东| 巴东县| 焦作市| 临汾市| 奉节县| 鄂伦春自治旗| 永康市| 纳雍县| 元朗区| 依安县| 偃师市| 共和县| 大同县| 调兵山市| 福清市| 西林县| 富源县| 开远市| 板桥市| 安顺市| 渝中区| 剑河县| 长宁县| 玉环县| 浮梁县|