1. 程式人生 > >淺談設計模式(一):狀態模式|外觀模式|代理模式

淺談設計模式(一):狀態模式|外觀模式|代理模式

前言

計劃開一個設計模式的系列,介紹常見的幾種設計模式,本文涉及的設計模式包含以下3種

  • 狀態模式:state pattern

  • 外觀模式:facade pattern

  • 代理模式:proxy pattern 

備註:下文適合看過《海賊王》的人閱讀,沒看過海賊王的觀眾請在父母陪同下閱讀

 

狀態模式:state pattern

在狀態模式的設計方案裡,一個主類(稱為context類),可以在內部狀態變化的時候一次性改變它的「所有行為」,而這個「所有行為」會被我們聚合到不同的類(state1,state2,state3)裡面去。

這個內部狀態我們可以理解為一個可以手動設定的state變數,設定它可以讓context內部的state1切換為state2,或者是從state2切換為state3。

這麼做,相比起傳統的程式碼邏輯會發生什麼變化呢?在傳統的程式碼裡,我們可能會在每個方法下,都寫一大段if-else的狀態判斷邏輯裡,然後對不同狀態分別做處理,這個時候程式碼非常鬆散,不利於閱讀和擴充套件,所以我們選擇以「狀態」為依據, 把這些if-else的每一部分都「聚合」到不同的狀態(不同的state類)裡面去,然後通過一個主類(context),去統一維護和管理。這樣,邏輯上就清晰了很多,也大大降低了維護和擴充套件的難度。 

Example

草帽路飛,是熱血漫《海賊王》的主角,像其他許多同類型的作品一樣,主角有自己不同層次戰鬥的狀態,進化過程如下所示

  1. 二檔:加速血液的流動,大幅提高速度和身體強度,代表大招是「橡膠Jet火箭炮」(拳)和 「橡膠Jet」(踢)

  2. 三檔:向橡膠的身體吹入空氣,使身體變成巨人,攻擊力大增。代表大招是「橡膠巨人火箭炮」(拳)和「橡膠巨人戰斧」(踢)

  3. 四檔:將武裝色霸氣和橡膠果實融合,攻擊和速度再次強化,代表大招是「橡膠獅子火箭炮」(拳)和「橡膠犀牛榴彈炮」(踢)

 

下圖描述的是主角路飛初次進化為「二檔」的歷史性時刻

 

我們發現,主角路飛擁有不同的戰鬥狀態:二檔,三檔,四檔,並且大招的使用是類似的,無非就是用拳頭還是用腳踢的問題,但是攻擊力和招式上都不同,我們可以根據這個狀態的統一性抽象一個state接口出來:

public interface State {
    // 拳打
    public void punch ();
    // 腳踢
    public void kick ();
}

然後建立二檔,三檔,四檔類,並且實現state介面

// 二檔
public class SecGearState implements State {
    public void punch () {
        System.out.println("二檔:橡膠Jet火箭炮");
    };
    public void kick () {
        System.out.println("二檔:橡膠Jet鞭");
    };
}
// 三檔
public class ThirdGearState implements State {
    public void punch () {
        System.out.println("三檔:橡膠巨人火箭炮");
    };
    public void kick () {
        System.out.println("三檔:橡膠巨人戰斧");
    };
}
// 四檔
public class FourGearState implements State{
    public void punch () {
        System.out.println("四檔:橡膠獅子火箭炮");
    };
    public void kick () {
        System.out.println("四檔:橡膠犀牛榴彈炮");
    };
}


最後,路飛可能會在戰鬥中隨時切換狀態,比如從二檔切換到三檔,或者從三檔切換到四檔,所以我們要設定一個Context類去管理,在這個類裡面,它有兩個功能

  1. 隨時切換狀態

  2. 代理呼叫狀態類的方法

public class Context {
    State state;
    // 隨時切換狀態
    public void setState(State state){
        this.state = state;
    }
    // 代理呼叫狀態類的方法
    public void punch () {
        state.punch();
    }
 
    public  void kick () {
        state.kick();
    }
}

測試

public class Test {
    public static void main(String args []) {
        State secGearState = new SecGearState();
        State thirdGearState = new ThirdGearState();
        State fourGearState = new FourGearState();
        Context context = new Context();
        // 路飛進化成二檔
        context.setState(secGearState);
        context.punch();
        context.kick();
        System.out.println("----------------");
        // 路飛進化成三檔
        context.setState(thirdGearState);
        context.punch();
        context.kick();
        System.out.println("----------------");
        // 路飛進化成四檔
        context.setState(fourGearState);
        context.punch();
        context.kick();
    }
}

輸出

 

外觀模式:facade pattern

外觀模式很簡單且容易理解,但理解之後卻非常有用。

說白了就是:把不同類的不同介面,統一代理到一個類裡面對外輸出,使程式碼具有良好的封裝性

 

Example

咱們還是拿海賊王的一個情境舉個例子

比如說,在海賊王367裡,草帽海賊團 VS 巨人殭屍奧茲 的時候,索隆,山治,佛蘭奇,烏索普和喬巴使用了一招非常精(you)彩(zhi)的技能:合體-大皇帝。

也就是說,合體後的草帽海賊團,在能夠使用每個人的絕招的同時,是作為“大皇帝”這個整體對外暴露的

我們使用外觀模式去實現的話,程式碼邏輯如下所示

 

首先每個成員我們用一個類去表示

// 索隆
public class Zoro {
    public void useSword () {
        System.out.println("三刀流斬擊");
    }
}
// 山治
public class Sanj {
    public void kick () {
        System.out.println("惡魔風腳");
    }
}
// 弗蘭奇
public class Franky {
    public void openFire () {
        System.out.println("風來炮");
    }
}
// 愛吃棉花糖的喬巴
public class QiaoBa {
    public void cure () {
        System.out.println("回血治療");
    }
}

 

然後我們用一個整體的類,去代理上面的每個成員類的邏輯

// 合體後的大皇帝
public class BigKing {
    Franky franky;
    QiaoBa qiaoba;
    Sanj sanj;
    Zoro zoro;
    public BigKing () {
        franky = new Franky();
        qiaoba = new QiaoBa();
        sanj = new Sanj();
        zoro = new Zoro();
    }
    // 索隆類的功能
    public void useSord () {
        zoro.useSword();
    }
    // 山治類的功能
    public void kick () {
        sanj.kick();
    }
    // 佛蘭奇類的功能
    public void openFire () {
        franky.openFire();
    }
    // 喬巴類的功能
    public void cure () {
        qiaoba.cure();
    }
}


測試

public class Test {
    public static void main(String args []) {
        BigKing bigking = new BigKing();
        bigking.useSord();
        bigking.kick();
        bigking.cure();
        bigking.openFire();
    }
}

輸出

附帶一張圖,hhhh

 

代理模式:proxy pattern

使用一個類接管另一個類所有的方法呼叫,同時能在原來類的方法呼叫前,加入一些自己的“中間邏輯”。這種方式被稱為代理模式。

 

假設類B 是類A的代理類,那麼在呼叫類B的方法的時候,實際還是通過類B去呼叫類A的介面,但是現在所有的「控制權」都已經牢牢掌握在類B手裡了,代理類B能夠很自由的加入一些中間邏輯。

 

顯然,類B和類A起到的功能是相同的,我們可以抽象一個介面,去讓原類(A )和代理類(B)去實現

 

Example

不好意思,這裡還是用我熟悉的海賊王打個比方,在七武海-多佛朗明哥剛剛出場的時候,他就用線線果實提供的能力,操控兩名海軍自相殘殺。

 

如果我們把海軍抽象為一個類的話,那麼多佛朗明哥就是「海軍類」的代理類了,實際上我們發現

  • 實質出手傷人的並不是海軍,而是多佛朗明哥,也就是代理類掌握了真正的控制權

  • 直接出手傷人的仍然是海軍,也就是代理類仍然呼叫的是原類的介面

 

程式碼如下

1.我們抽象一個海軍戰士的接口出來

// 海軍戰士介面
public interface NavyFighter {
    // 使用刀劍
    public void useSword ();
    // 徒手格鬥
    public void fight();
    // 使用槍炮
    public void useGuns();
}

2.讓海軍軍官實現這個介面

public class NavyCaptain implements NavyFighter {
    String name = "海軍上尉";
    // 海軍裝備預算不夠,不能購買二十一大快刀
    public void useSword() {
      System.out.println(name +"發動了一次普通的斬擊");
    }
    // 沒有果實能力,只能徒手格鬥了
    public void fight() {
        System.out.println(name + "發動了一次普通的拳擊");
    }
 
    public void useGuns() {
        System.out.println(name +"打出了一發普通的海樓石子彈");
    }
}

3.讓多佛朗明哥也實現這個介面

public class Doflamingo implements NavyFighter {
    NavyFighter navyFighter;
    public Doflamingo (NavyFighter navyFighter) {
        this.navyFighter = navyFighter;
    }
 
    public void useSword() {
        System.out.print("在多佛朗明哥操控下,");
      this.navyFighter.useSword();
    }
 
    public void fight() {
        System.out.print("在多佛朗明哥操控下,");
      this.navyFighter.fight();
    }
 
    public void useGuns() {
        System.out.print("在多佛朗明哥操控下,");
        this.navyFighter.useGuns();
    }
}
 

測試

public class Test {
    public static void main(String args []) {
        NavyFighter navyCaptain = new NavyCaptain();
        navyCaptain.useSword();
        navyCaptain.fight();
        navyCaptain.useGuns();
        System.out.println("-----------------------------");
        NavyFighter doflamingo = new Doflamingo(navyCaptain);
        doflamingo.useSword();
        doflamingo.fight();
        doflamingo.useSword();
    }
}

 

輸出