設計模式之策略模式
設計模式之策略模式
定義:定義了算法族,分別封裝起來,讓它們之間可以相互替換,此模式讓算法的變法獨立於使用算法的客戶。
關於模式的學習本來就是比較頭痛的事情,如果但看看理論。就算你知道了它應用的場景。在實際的應用中,你也很難的應用到你的項目中。理解容易,實際的應用更難。
接下來我要通過一個實例來講解這個設計模式。通過不斷的需求變化,來真正的體現設計模式帶來的好處,我們使用一些設計模式,就是讓你的程序可以應付客戶的不斷的需求變化。我們知道,在我們實際的開發中,難的不是技術上的問題,而是客戶隨着開發的進行,他們的需求的不斷的變化,這是最頭痛的,如果你的程序不能對付客戶的變化的話,你就會付出更多的代價。爲了讓我們的程序可以健壯,重用,可擴展。利用設計模式的思想去開發編程,可以解決上面的幾個問題。這並不是唯一的辦法,但是比較好的辦法。
我們的要求是設計一個模擬鴨子游戲的應用程序。(注:這個應用是在Head First 設計模式 一書中的例子)。遊戲中會出現各種鴨子,一邊游泳戲水,一邊呱呱叫。對於這樣的要求,我們首先會運用OO技術,並利用繼承的思想,設計一個鴨子的超類(Superclass),並讓各種鴨子繼承這個超類。
public class Duck{
public void quack(){ //呱呱叫
System.out.println("呱呱叫");
}
public void swim(){ //游泳
System.out.println(" 游泳");
}
public abstract void display(); /*因爲外觀不一樣,讓子類自己去決定了。*/
}
對於它的子類只需簡單的繼承就可以了,並實現自己的display()方法。
//野鴨
public class MallardDuck extends Duck{
public void display(){
System.out.println("野鴨的顏色...");
}
}
//紅頭鴨
public class RedheadDuck extends Duck{
public void display(){
System.out.println("紅頭鴨的顏色...");
}
}
這裏可能還會有其他顏色的鴨子。通過這個簡單的繼承我們滿足了客戶的要求。
不幸的是,現在客戶又提出了新的需求,想讓鴨子飛起來。這個對於我們OO程序員,在簡單不過了,在超類中在加一個方法就可以了。
public class Duck{
public void quack(){ //呱呱叫
System.out.println("呱呱叫");
}
public void swim(){ //游泳
System.out.println(" 游泳");
}
public abstract void display(); /*因爲外觀不一樣,讓子類自己去決定了。*/
public void fly(){
System.out.println("飛吧!鴨子");
}
}
在子類中只需簡單的覆蓋。
//殘廢鴨
public class DisabledDuck extends Duck{
public void display(){
System.out.println("殘廢鴨的顏色...");
}
public void fly(){
//覆蓋,變成什麼事都不做。
}
}
其它會飛的鴨子不用覆蓋。
這樣所有的繼承這個超類的鴨子都會fly了。但是問題又出來了,客戶又提出有的鴨子會飛,有的不能飛。客戶就是那樣的讓我們頭痛啊,他們的需求會隨着我們的開發而改變的。
這時我們需要做的是對會飛的鴨子重寫fly方法,對於不能飛的也重寫,但在方法裏什麼也不寫,只是一個空方法,沒有任何實現。只是簡單覆蓋超類中的fly方法。這樣可以滿足暫時的需求了,但對我們的維護帶來了麻煩。對於一些不會叫的,也不會飛的鴨子的子類中,夾雜了一些沒有意義的代碼,就是對一些超類方法的覆蓋。
對於上面的設計,你可能發現一些弊端,如果超類有新的特性,子類都必須變動,這是我們開發最不喜歡看到的,一個類變讓另一個類也跟着變,這有點不符合OO設計了。這樣很顯然的耦合了一起。
這時,我們會馬上想到運用接口來消除這種弊端。我們把容易引起變法的部分提取出來並封裝之,來應付以後的變法。雖然代碼量加大了,但可用性提高了,耦合度也降低了。同時也具有很強的擴展性,也真正的利用了OO設計思想。
我們把Duck中的fly方法和quack提取出來。
public interface Flyable{
public void fly();
}
public interface Quackable{
public void quack();
}
最後Duck的設計成爲:
public class Duck{
public void swim(){ //游泳
System.out.println(" 游泳");
}
public abstract void display(); /*因爲外觀不一樣,讓子類自 己去決定了。*/
}
而MallardDuck,RedheadDuck,DisabledDuck 就可以寫成爲:
//野鴨
public class MallardDuck extends Duck implements Flyable,Quackable{
public void display(){
System.out.println("野鴨的顏色...");
}
public void fly(){
//實現該方法
}
public void quack(){
//實現該方法
}
}
//紅頭鴨
public class RedheadDuck extends Duck implements Flyable,Quackable{
public void display(){
System.out.println("紅頭鴨的顏色...");
}
public void fly(){
//實現該方法
}
public void quack(){
//實現該方法
}
}
//殘廢鴨 只實現Quackable(能叫不能飛)
public class DisabledDuck extends Duck implements Quackable{
public void display(){
System.out.println("殘廢鴨的顏色...");
}
public void quack(){
//實現該方法
}
}
這樣已設計,我們的程序就降低了它們之間的耦合。
現在我們知道使用繼承並不能很好的解決問題,因爲鴨子的行爲在子類中不斷的變化,並且讓所用的子類都有這些行爲是不恰當的。Flyable和Quackable接口一開始似乎還挺不錯的,解決了問題(只有會飛到鴨子才實現 Flyable),但是Java接口不具有實現代碼,所以實現接口無法達到代碼的複用。這時我們有一個設計原則:找出應用中可能需要變化之處,把它們獨立出來,不要和那些不需要變化的代碼混在一起。
現在我們根據這個設計原則,我們上面的設計還存在問題。我們因該如何解決呢?
現在,爲了要分開“變化和不變化的部分”,我們準備建立兩組類(完全遠離Duck類),一個是"fly"相關的,另一個是“quack”相關的,每一組類將實現各自的動作。比方說,我們可能有一個類實現“呱呱叫”,另一個類實現“吱吱叫”,還有一個類實現“安靜”。
我們如何設計實現飛行和呱呱叫的行爲的類呢?我們爲了讓我們的程序有彈性,減小耦合。我們又必須遵循第二個設計原則:針對接口編程,而不是針對實現編程。
看看我們具體的實現吧,實現勝於理論。
首先寫兩個接口。FlyBehavior(飛行行爲)和QuackBehavior(叫的行爲).
public interface FlyBehavior{
public void fly();
}
public interface QuackBehavior{
public void quack();
}
我們在定義一些針對FlyBehavior的具體實現。
public class FlyWithWings implements FlyBehavior{
public void fly(){
//實現了所有有翅膀的鴨子飛行行爲。
}
}
public class FlyNoWay implements FlyBehavior{
public void fly(){
//什麼都不做,不會飛
}
}
針對QuackBehavior的幾種具體實現。
public class Quack implements QuackBehavior{
public void quack(){
//實現呱呱叫的鴨子
}
}
public class Squeak implements QuackBehavior{
public void quack(){
//實現吱吱叫的鴨子
}
}
public class MuteQuack implements QuackBehavior{
public void quack(){
//什麼都不做,不會叫
}
}
這樣的設計,可以讓飛行和呱呱叫的動作被其他的對象複用,因爲這些行爲已經與鴨子類無關了。而我們增加一些新的行爲,不會影響到既有的行爲類,也不會影響“使用”到飛行行爲的鴨子類。
最後我們看看Duck 如何設計。
public class Duck{
FlyBehavior flyBehavior;//接口
QuackBehavior quackBehavior;//接口
public Duck(){}
public abstract void display();
public void swim(){
//實現游泳的行爲
}
public void performFly(){
flyBehavior.fly();//這時鴨子對象不親自處理飛行行爲,而是委託給flyBehavior引用的對象。
}
public void performQuack(){
quackBehavior.quack();();//這時鴨子對象不親自處理叫的行爲,而是委託給quackBehavior引用的對象。
}
}
看看MallardDuck如何實現。
public class MallardDuck extends Duck{
public MallardDuck() {
flyBehavior = new FlyWithWings ();
quackBehavior = new Quack();
//因爲MallardDuck 繼承了Duck,所有具有flyBehavior 與quackBehavior 實例變量}
public void display(){
//實現
}
}
這樣就滿足了即可以飛,又可以叫,同時展現自己的顏色了。
這樣的設計我們可以看到是把flyBehavior ,quackBehavior 的實例化寫在子類了。我們還可以動態的來決定。
我們只需在Duck中加上兩個方法。
public class Duck{
FlyBehavior flyBehavior;//接口
QuackBehavior quackBehavior;//接口
public void setFlyBehavior(FlyBehavior flyBehavior){
this.flyBehavior = flyBehavior;
}
public void setQuackBehavior(QuackBehavior quackBehavior {
this.quackBehavior= quackBehavior;
}
}
大家看到這樣的方法應該知道它是利用依賴注入的思想,動態的來改變鴨子的行爲。
在測試的類中,我們可以這樣寫。
public class Test{
public static void main(String[] args){
//一般的用法:
Duck mallard = new MallardDuck();
mallard.performFly();
mallard .perforeQuack();
//動態的改變
mallard.setFlyBehavior(new FlyRocketPowered);//具有火箭動力的飛行能力
mallard.performFly();
}
}
這樣的用法其實就是Spring 中的一個核心機制,Ioc依賴注入思想。
我們講了這麼些,其實就是策略模式的應用。我們寫了這麼多,就是爲了讓你更加明白這個設計模式的應用場景。
策略模式:定義了算法族(飛行行爲中的各種實現<看成一族算法>和呱呱叫行爲中的各種實現<看成一族算法>),分別分裝起來,讓它們之間可以相互調用,此模式讓算法的變法(就是飛行行爲和呱呱叫的行爲)獨立於使用算法的客戶。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.