「補課」進行時:設計模式(16)——簡單又實用的門面模式

1. 前文彙總

「補課」進行時:設計模式系列java

2. 從銀行轉帳提及

當咱們在銀行進行轉帳操做的時候,整個操做流程咱們能夠簡化爲帳戶 A 扣費,而後帳戶 B 增長餘額,最後轉帳操做成功。編程

這兩個操做缺一不可,同時又不能顛倒順序。設計模式

簡單定義一個轉帳的接口 ITransfer安全

public interface ITransfer {
    // 首先發起轉帳
    void start(String amount);
    // 帳戶 A 進行扣費
    void subtractionA();
    // 帳戶 B 增長金額
    void addB();
    // 轉帳完成
    void end();
}

而後增長一個接口實現類:架構

public class TransferImpl implements ITransfer {
    @Override
    public void start(String amount) {
        System.out.println(String.format("帳戶 A 開始向帳戶 B 進行轉帳: %s 元。", amount));
    }

    @Override
    public void subtractionA() {
        System.out.println("帳戶 A 扣費成功");
    }

    @Override
    public void addB() {
        System.out.println("帳戶 B 餘額增長成功");
    }

    @Override
    public void end() {
        System.out.println("轉帳完成");
    }
}

來一個測試類:ide

public class Test {
    public static void main(String[] args) {
        ITransfer transfer = new TransferImpl();
        transfer.start("1000");
        transfer.subtractionA();
        transfer.addB();
        transfer.end();
    }
}

最後運行的結果以下:測試

帳戶 A 開始向帳戶 B 進行轉帳: 1000 元。
帳戶 A 扣費成功
帳戶 B 餘額增長成功
轉帳完成

咱們回過頭來看看這個過程,它與高內聚的要求相差甚遠,更不要說迪米特法則、接口隔離原則了。this

若是咱們要進行轉帳操做,那麼咱們必需要知道這幾個步驟,並且還要知道它們的順序,一旦出錯,轉帳操做就沒法完成,這在面向對象的編程中是極度地不適合,它根本就沒有完成一個類所具備的單一職責。設計

那怎麼辦呢?這時候銀行櫃檯出現了,咱們只須要把需求告訴銀行櫃檯,櫃檯會直接幫咱們完成轉帳操做。3d

銀行櫃檯:

public class BankCounter {
    private ITransfer transfer = new TransferImpl();
    // 轉帳操做一體化
    public void transferAmount(String amount) {
        transfer.start(amount);
        transfer.subtractionA();
        transfer.addB();
        transfer.end();
    }
}

接下來修改一下測試類:

public class Test1 {
    public static void main(String[] args) {
        BankCounter counter = new BankCounter();
        counter.transferAmount("1000");
    }
}

和剛纔的執行結果同樣,可是整個測試類卻簡化了不少,只要關心和銀行櫃檯進行交互就行,徹底不用本身操心以前的帳戶 A 扣費,再給帳戶 B 加餘額,可是,每次轉帳就這麼直接轉帳有點不大安全,假如帳戶 A 的餘額根本不足轉帳的費用,那麼就不該該轉帳成功。

增長一個餘額校驗類 Balance 對帳戶餘額進行校驗:

public class Balance {
    Boolean checkBalance() {
        System.out.println("帳戶餘額校驗成功");
        return true;
    }
}

這時候,測試類無需改動,只需修改銀行櫃檯類就能夠:

public class BankCounter {
    private ITransfer transfer = new TransferImpl();
    private Balance balance = new Balance();
    // 轉帳操做一體化
    public void transferAmount(String amount) {
        transfer.start(amount);
        transfer.subtractionA();
        // 增長餘額校驗
        if (balance.checkBalance()) {
            transfer.addB();
            transfer.end();
        }
    }
}

這裏只增長了一個餘額校驗類,而且對轉帳的過程進行了修改,這個過程對於咱們來說是徹底透明的,咱們徹底不須要關心轉帳的過程,這個過程由銀行櫃檯所有幫咱們辦好了。

高層模塊沒有任何改動,可是帳戶的餘額已經被檢查過了,不改變子系統對外暴露的接口、方法,只改變內部的處理邏輯,其餘兄弟模塊的調用產生了不一樣的結果。

是否是很是簡單,沒錯,這就是門面模式或者說外觀模式。

3. 門面模式

3.1 定義

門面模式(Facade Pattern)也叫作外觀模式,是一種比較經常使用的封裝模式,其定義以下:

Provide a unified interface to a set of interfaces in a subsystem.Facadedefines a higher-level interface that makes the subsystem easier to use.(要求一個子系統的外部與其內部的通訊必須經過一個統一的對象進行。門面模式提供一個高層次的接口,使得子系統更易於使用。)

3.2 通用類圖

門面模式注重「統一的對象」,也就是提供一個訪問子系統的接口,除了這個接口不容許有任何訪問子系統的行爲發生,其通用類圖:

是的,類圖就這麼簡單,可是它表明的意義但是異常複雜,Subsystem Classes是子系統全部類的簡稱,它可能表明一個類,也可能表明幾十個對象的集合。甭管多少對象,咱們把這些對象所有圈入子系統的範疇:

再簡單地說,門面對象是外界訪問子系統內部的惟一通道,無論子系統內部是多麼雜亂無章,只要有門面對象在,就能夠作到「金玉其外,敗絮其中」。咱們先明確一下門面模式的角色。

  • Facade 門面角色:此角色知曉子系統的全部功能和責任。通常狀況下,本角色會將全部從客戶端發來的請求委派到相應的子系統去,也就說該角色沒有實際的業務邏輯,只是一個委託類。
  • subsystem 子系統角色:能夠同時有一個或者多個子系統。每個子系統都不是一個單獨的類,而是一個類的集合。子系統並不知道門面的存在。對於子系統而言,門面僅僅是另一個客戶端而已。

3.3 通用代碼

子系統:

// 
public class ClassA {
    public void doSomethingA() {
        // 執行邏輯 A
    }
}

public class ClassB {
    public void doSomethingB() {
        // 執行邏輯 A
    }
}

public class ClassC {
    public void doSomethingC() {
        // 執行邏輯 A
    }
}

門面類:

public class Facade {
    private ClassA classA = new ClassA();
    private ClassB classB = new ClassB();
    private ClassC classC = new ClassC();
    public void methodA() {
        this.classA.doSomethingA();
    }
    public void methodB() {
        this.classB.doSomethingB();
    }
    public void methodC() {
        this.classC.doSomethingC();
    }
}

4. 注意

有一點須要注意的是:門面不參與子系統內的業務邏輯。

這句話怎麼理解?舉一個簡單的例子:

把上面的通用代碼稍微改一下,在 methodC() 方法上先調用 ClassAdoSomethingA() 方法,而後再調用 ClassCdoSomethingC() 方法,修改後的門面類以下:

public class Facade {
    private ClassA classA = new ClassA();
    private ClassB classB = new ClassB();
    private ClassC classC = new ClassC();
    public void methodA() {
        this.classA.doSomethingA();
    }
    public void methodB() {
        this.classB.doSomethingB();
    }
    public void methodC() {
        this.classA.doSomethingA();
        this.classC.doSomethingC();
    }
}

很是簡單,只是在 methodC() 方法中增長了 doSomethingA() 方法的調用,能夠這樣作嗎?

我相信在大多數的平常開發中,咱們不少時候都是直接這麼寫了,這麼寫有什麼問題麼?

固然有,由於這種作法讓門面對象參與了業務邏輯,門面對象只是提供一個訪問子系統的一個路徑而已,它不該該也不能參與具體的業務邏輯,不然就會產生一個倒依賴的問題:子系統必須依賴門面才能被訪問。

那麼在這種狀況下能夠怎麼處理呢?

也很簡單,建立一個封裝類,封裝完畢後提供給門面對象:

public class Context {
    private ClassA classA = new ClassA();
    private ClassC classC = new ClassC();
    // 複雜的業務操做
    public void complexMethod() {
        this.classA.doSomethingA();
        this.classC.doSomethingC();
    }
}

這個封裝類存在的價值就是產生一個複雜的業務規則 complexMethod() ,而且它的生存環境是在子系統內,僅僅依賴兩個相關的對象,門面對象經過對它的訪問完成一個複雜的業務邏輯,最後咱們經過門面模式進行調用的時候直接調用封裝類:

public class Facade1 {
    private ClassA classA = new ClassA();
    private ClassB classB = new ClassB();
    private Context context = new Context();
    public void methodA() {
        this.classA.doSomethingA();
    }
    public void methodB() {
        this.classB.doSomethingB();
    }
    public void methodC() {
        this.context.complexMethod();
    }
}

經過這樣一次封裝後,門面對象又不參與業務邏輯了,在門面模式中,門面角色應該是穩定,它不該該常常變化,一個系統一旦投入運行它就不該該被改變,它是一個系統對外的接口,你變來變去還怎麼保證其餘模塊的穩定運行呢?可是,業務邏輯是會常常變化的,咱們已經把它的變化封裝在子系統內部,不管你如何變化,對外界的訪問者來講,都仍是同一個門面,一樣的方法——這纔是架構師最但願看到的結構。

相關文章
相關標籤/搜索