java與設計模式-門面模式(外觀模式)

java與設計模式-門面模式(外觀模式)

一、定義

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

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

二、通用類圖

在這裏插入圖片描述

門面模式注重“統一的對象”, 也就是提供一個訪問子系統的接口, 除了這個接口不允許有任何訪問子系統的行爲發生。

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

在這裏插入圖片描述

再簡單地說, 門面對象是外界訪問子系統內部的唯一通道, 不管子系統內部是多麼雜亂無章, 只要有門面對象在, 就可以做到“金玉其外, 敗絮其中”。

三、角色分析

  • Facade門面角色

客戶端可以調用這個角色的方法。 此角色知曉子系統的所有功能和責任。 一般情況下,本角色會將所有從客戶端發來的請求委派到相應的子系統去, 也就說該角色沒有實際的業務邏輯, 只是一個委託類。

  • subsystem子系統角色

可以同時有一個或者多個子系統。 每一個子系統都不是一個單獨的類, 而是一個類的集合。 子系統並不知道門面的存在。 對於子系統而言, 門面僅僅是另外一個客戶端而已。

四、經典代碼實現

業務子系統

public class ClassA {

    public void doSomethingA() {
        System.out.println("class a 業務邏輯處理...");
    }
}
public class ClassB {

    public void doSomethingB() {
        System.out.println("class b 業務邏輯處理...");
    }
}
public class ClassC {

    public void doSomethingC() {
        System.out.println("class c 業務邏輯處理...");
    }
}

門面角色

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();
    }

}

五、門面模式的應用

5.1 門面模式的優點

  • 減少系統的相互依賴

想想看, 如果我們不使用門面模式, 外界訪問直接深入到子系統內部, 相互之間是一種強耦合關係, 你死我就死, 你活我才能活, 這樣的強依賴是系統設計所不能接受的, 門面模式的出現就很好地解決了該問題, 所有的依賴都是對門面對象的依賴, 與子系統無關.

  • 提高了靈活性

依賴減少了, 靈活性自然提高了。 不管子系統內部如何變化, 只要不影響到門面對象,任你自由活動。

  • 提高安全性

想讓你訪問子系統的哪些業務就開通哪些邏輯, 不在門面上開通的方法, 你休想訪問到。

5.2 門面模式的缺點

門面模式最大的缺點就是不符合開閉原則, 對修改關閉, 對擴展開放, 看看我們那個門面對象吧, 它可是重中之重, 一旦在系統投產後發現有一個小錯誤, 你怎麼解決? 完全遵從開閉原則, 根本沒辦法解決。 繼承? 覆寫? 都頂不上用, 唯一能做的一件事就是修改門面角色的代碼, 這個風險相當大, 這就需要大家在設計的時候慎之又慎, 多思考幾遍纔會有好收穫.

5.3 門面模式的使用場景

  • 爲一個複雜的模塊或子系統提供一個供外界訪問的接口
  • 子系統相對獨立——外界對子系統的訪問只要黑箱操作即可

比如利息的計算問題, 沒有深厚的業務知識和紮實的技術水平是不可能開發出該子系統的, 但是對於使用該系統的開發人員來說, 他需要做的就是輸入金額以及存期, 其他的都不用關心, 返回的結果就是利息, 這時候, 門面模式是非使用不可了.

  • 預防低水平人員帶來的風險擴散

比如一個低水平的技術人員參與項目開發, 爲降低個人代碼質量對整體項目的影響風險, 一般的做法是“畫地爲牢”, 只能在指定的子系統中開發, 然後再提供門面接口進行訪問操作。

5.4 門面模式的注意事項

  • 一個子系統可以有多個門面

一般情況下, 一個子系統只要有一個門面足夠了, 在什麼情況下一個子系統有多個門面呢? 以下列舉了幾個例子。

門面已經龐大到不能忍受的程度

比如一個純潔的門面對象已經超過了200行的代碼, 雖然都是非常簡單的委託操作, 也建議拆分成多個門面, 否則會給以後的維護和擴展帶來不必要的麻煩。 那怎麼拆分呢? 按照功能拆分是一個非常好的原則, 比如一個數據庫操作的門面可以拆分爲查詢門面、 刪除門面、 更新門面等。

子系統可以提供不同訪問路徑

我們以門面模式的通用源代碼爲例。 ClassA、 ClassB、 ClassC是一個子系統的中3個對象, 現在有兩個不同的高層模塊來訪問該子系統, 模塊一可以完整的訪問所有業務邏輯, 也就是通用代碼中的Facade類, 它是子系統的信任模塊; 而模塊二屬於受限訪問對象, 只能訪問methodB方法, 那該如何處理呢? 在這種情況下, 就需要建立兩個門面以供不同的高層模塊來訪問, 在原有的通用源碼上增加一個新的門面即可。

public class Facaed2 {
    private Facade facade = new Facade();
    
    private void methodB() {
        this.facade.methodB();
    }

}

增加的門面非常簡單, 委託給了已經存在的門面對象Facade進行處理, 爲什麼要使用委託而不再編寫一個委託到子系統的方法呢? 那是因爲在面向對象的編程中, 儘量保持相同的代碼只編寫一遍, 避免以後到處修改相似代碼的悲劇.

  • 門面不參與子系統內的業務邏輯

舉一個例子來說明, 還是以通用源代碼爲例。 我們把門面上的methodC上的邏輯修改一下, 它必須先調用ClassA的doSomethingA方法, 然後再調用ClassC的doSomethingC方法。

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()方法的調用, 可以這樣做嗎? 我相信大部分讀者都說可以這樣做, 而且已經在實際系統開發中這樣使用了, 我今天告訴各位, 這樣設計是非常不靠譜的, 爲什麼呢? 因爲你已經讓門面對象參與了業務邏輯, 門面對象只是提供一個訪問子系統的一個路徑而已, 它不應該也不能參與具體的業務邏輯, 否則就會產生一個倒依賴的問題: 子系統必須依賴門面才能被訪問, 這是設計上一個嚴重錯誤, 不僅違反了單一職責原則, 同時也破壞了系統的封裝性。

說了這麼多, 那對於這種情況該怎麼處理呢? 建立一個封裝類, 封裝完畢後提供給門面對象。 我們先建立一個封裝類,如下。

// 封裝類
class Context {

    private ClassA classA = new ClassA();
    private ClassC classC = new ClassC();

    public void complexMethod() {
        this.classA.doSomethingA();
        this.classC.doSomethingC();
    }

}

該封裝類的作用就是產生一個業務規則complexMethod, 並且它的生存環境是在子系統內, 僅僅依賴兩個相關的對象, 門面對象通過對它的訪問完成一個複雜的業務邏輯。

改造後的Facade類

public class Facade {

    /**
     * 被委託的對象
     */

    private ClassA classA = new ClassA();

    private ClassB classB = new ClassB();

    private ClassC classC = new ClassC();
    
    private Context context = new Context();


    /**
     * 提供給外部的接口
     */

    public void methodA() {
        this.classA.doSomethingA();
    }

    public void methodB() {
        this.classB.doSomethingB();
    }

    public void methodC() {
        // this.classA.doSomethingA();
        // this.classC.doSomethingC();
        
        this.context.complexMethod();
    }

}

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章