【白話設計模式二】外觀模式(Facade)

#0 系列目錄#算法

#1 場景問題# ##1.1 生活中的示例## 外觀模式在現實生活中的示例不少,好比:組裝電腦,一般會有兩種方案。設計模式

一個方案是去電子市場把本身須要的配件都買回來,而後本身組裝,絕對DIY(Do It Yourself)。這個方案好是好,可是須要對各類配件都要比較熟悉,這樣才能選擇最合適的配件,並且還要考慮配件之間的兼容性。如圖所示:架構

輸入圖片說明

另一個方案,就是到電子市場,找一家專業裝機的公司,把具體的要求一講,而後就等着拿電腦就行了。固然價格會比本身所有DIY貴一些,但綜合起來這還算是個不錯的選擇,估計也是大多數人的選擇。如圖所示:工具

輸入圖片說明

這個專業的裝機公司就至關於本章的主角——Facade。有了它,咱們就不用本身去跟衆多賣配件的公司打交道,只須要跟裝機公司交互就能夠了,由裝機公司去跟各個賣配件的公司交互,並組裝好電腦返回給咱們。學習

把上面的過程抽象一下,若是把電子市場當作是一個系統,而各個賣配件的公司當作是模塊的話,就相似於出現了這樣一種狀況:客戶端爲了完成某個功能,須要去調用某個系統中的多個模塊,把它們稱爲是A模塊、B模塊和C模塊吧,對於客戶端而言,那就須要知道A、B、C這三個模塊的功能,還須要知道如何組合這多個模塊提供的功能來實現本身所須要的功能,很是麻煩。ui

要是有一個簡單的方式能讓客戶端去實現相同的功能多好啊,這樣,客戶端就不用跟系統中的多個模塊交互,並且客戶端也不須要知道那麼多模塊的細節功能了,實現這個功能的就是Facade。this

##1.2 代碼生成的應用## 不少公司都有這樣的應用工具,能根據配置生成代碼。通常這種工具都是公司內部使用,較爲專有的工具,生成的可能是按照公司的開發結構來實現的常見的基礎功能,好比增刪改查。這樣每次在開發實際應用的時候,就能夠以很快的速度把基本的增刪改查實現出來,而後把主要的精力都放在業務功能的實現上。.net

固然這裏不可能去實現一個這樣的代碼生成工具,那須要整本書的內容,這裏僅用它來講明外觀模式。設計

假設使用代碼生成出來的每一個模塊都具備基本的三層架構,分爲:表現層、邏輯層和數據層,那麼代碼生成工具裏面就應該有相應的代碼生成處理模塊。代理

另外,代碼生成工具自身還須要一個配置管理的模塊,經過配置來告訴代碼生成工具,每一個模塊究竟須要生成哪些層的代碼,好比:經過配置來描述,是隻須要生成表現層代碼呢,仍是三層都生成。具體的模塊示意如圖所示:

輸入圖片說明

那麼如今客戶端須要使用這個代碼生成工具來生成須要的基礎代碼,該如何實現呢?

##1.3 不用模式的解決方案## 有朋友會想,開發一個這樣的工具或許會比較麻煩,可是使用一下,應該不難吧,直接調用不就能夠了。

在示範客戶端以前,先來把工具模擬示範出來,爲了簡單,每一個模塊就寫一個類,並且每一個類只是實現一個功能,僅僅作一個示範。

  1. 先看看描述配置的數據Model,示例代碼以下:
/**
 * 示意配置描述的數據Model,真實的配置數據會不少
 */
public class ConfigModel {
    /**
     * 是否須要生成表現層,默認是true
     */
    private boolean needGenPresentation = true;
    /**
     * 是否須要生成邏輯層,默認是true
     */
    private boolean needGenBusiness = true;
    /**
     * 是否須要生成DAO,默認是true
     */
    private boolean needGenDAO = true;
    public boolean isNeedGenPresentation() {
        return needGenPresentation;
    }
    public void setNeedGenPresentation(
        this.needGenPresentation = needGenPresentation;
    }
    public boolean isNeedGenBusiness() {
        return needGenBusiness;
    }
    public void setNeedGenBusiness(boolean needGenBusiness) {
        this.needGenBusiness = needGenBusiness;
    }
    public boolean isNeedGenDAO() {
        return needGenDAO;
    }
    public void setNeedGenDAO(boolean needGenDAO) {
        this.needGenDAO = needGenDAO;
    }
}
  1. 接下來看看配置管理的實現示意,示例代碼以下:
/**
 * 示意配置管理,就是負責讀取配置文件,
 * 並把配置文件的內容設置到配置Model中去,是個單例
 */
public class ConfigManager {
    private static ConfigManager manager = null;
    private static ConfigModel cm = null;
    private ConfigManager(){
        //
    }
    public static ConfigManager getInstance(){
        if(manager == null){
            manager = new ConfigManager();
            cm = new ConfigModel();
            //讀取配置文件,把值設置到ConfigModel中去,這裏省略了
        }
        return manager;
    }
    /**
     * 獲取配置的數據
     * [@return](http://my.oschina.net/u/556800) 配置的數據
     */
    public ConfigModel getConfigData(){
        return cm;
    }
}
  1. 接下來就來看看各個生成代碼的模塊,在示意中,它們的實現相似,就是獲取配置文件的內容,而後按照配置來生成相應的代碼。分別看看它們的示意實現,先看生成表現層的示意實現,示例代碼以下:
/**
 * 示意生成表現層的模塊
 */
public class Presentation {
    public void generate(){
        //1:從配置管理裏面獲取相應的配置信息
        ConfigModel cm = ConfigManager.getInstance().getConfigData();
        if(cm.isNeedGenPresentation()){
            //2:按照要求去生成相應的代碼,並保存成文件
            System.out.println("正在生成表現層代碼文件");
        }
    }
}

/**
 * 示意生成邏輯層的模塊
 */
public class Business {
    public void generate(){
        ConfigModel cm = ConfigManager.getInstance().getConfigData();
        if(cm.isNeedGenBusiness()){
            System.out.println("正在生成邏輯層代碼文件");
        }
    }
}

/**
 * 示意生成數據層的模塊
 */
public class DAO {
    public void generate(){
        ConfigModel cm = ConfigManager.getInstance().getConfigData();
        if(cm.isNeedGenDAO()){
            System.out.println("正在生成數據層代碼文件");
        }
    }
}
  1. 此時的客戶端實現,就應該自行去調用這多個模塊了,示例代碼以下:
public class Client {
    public static void main(String[] args) {
        //如今沒有配置文件,就直接使用默認的配置,一般狀況下,三層都應該生成,
        //也就是說客戶端必須對這些模塊都有了解,纔可以正確使用它們
        new Presentation().generate();
        new Business().generate();
        new DAO().generate();
    }
}

##1.4 有何問題## 仔細查看上面的實現,會發現其中有一個問題:那就是客戶端爲了使用生成代碼的功能,須要與生成代碼子系統內部的多個模塊交互

這對於客戶端而言,是個麻煩,使得客戶端不能簡單的使用生成代碼的功能。並且,若是其中的某個模塊發生了變化,還可能會引發客戶端也須要跟着變化。那麼如何實現,才能讓子系統外部的客戶端在使用子系統的時候,既能簡單的使用這些子系統內部的模塊功能,而又不用客戶端去與子系統內的多個模塊交互呢?

#2 解決方案# ##2.1 外觀模式來解決## 用來解決上述問題的一個合理的解決方案就是外觀模式。那麼什麼是外觀模式呢?

  1. 外觀模式定義

輸入圖片說明

這裏先對兩個詞進行一下說明,一個是界面,一個是接口

一提到界面,估計不少朋友的第一反應就是圖形界面(GUI)。其實在這裏提到的界面,主要指的是從一個組件外部來看這個組件,可以看到什麼,這就是這個組件的界面,也就是所說的外觀。

好比:你從一個類外部來看這個類,那麼這個類的public方法就是這個類的外觀,由於你從類外部來看這個類,就能看到這些。

再好比:你從一個模塊外部來看這個模塊,那麼這個模塊對外的接口就是這個模塊的外觀,由於你就只能看到這些接口,其它的模塊內部實現的東西是被接口封裝隔離了的。

一提到接口,作Java的朋友的第一反應就是interface。其實在這裏提到的接口,主要是指的外部和內部交互的這麼一個通道,一般是指的一些方法,能夠是類的方法,也能夠是interface的方法。也就是說,這裏說的接口,並不等價於interface,也有多是一個類。

  1. 應用外觀模式來解決的思路

仔細分析上面的問題,客戶端想要操做更簡單點,那就根據客戶端的須要來給客戶端定義一個簡單的接口,而後讓客戶端調用這個接口,剩下的事情就不用客戶端管,這樣客戶端就變得簡單了。

固然,這裏所說的接口就是客戶端和被訪問的系統之間的一個通道,並不必定是指Java的interface。事實上,這裏所說的接口,在外觀模式裏面,一般指的是類,這個類被稱爲「外觀」。

外觀模式就是經過引入這麼一個外觀類,在這個類裏面定義客戶端想要的簡單的方法,而後在這些方法的實現裏面,由外觀類再去分別調用內部的多個模塊來實現功能,從而讓客戶端變得簡單,這樣一來,客戶端就只須要和外觀類交互就能夠了。

##2.2 模式結構和說明## 外觀模式的結構如圖所示:

輸入圖片說明

Facade:定義子系統的多個模塊對外的高層接口,一般須要調用內部多個模塊,從而把客戶的請求代理給適當的子系統對象。 模塊:接受Facade對象的委派,真正實現功能,各個模塊之間可能有交互。可是請注意,Facade對象知道各個模塊,可是各個模塊不該該知道Facade對象

##2.3 外觀模式示例代碼## 因爲外觀模式的結構圖過於抽象,所以把它稍稍具體點,假設子系統內有三個模塊,分別是AModule、BModule和CModule,它們分別有一個示意的方法,那麼此時示例的總體結構如圖所示:

輸入圖片說明

  1. 首先定義A模塊的接口,A模塊對外提供功能方法,從抽象的高度去看,能夠是任意的功能方法,示例代碼以下:
/**
 * A模塊的接口
 */
public interface AModuleApi {
    /**
     * 示意方法,A模塊對外的一個功能方法
     */
    public void testA();
}
  1. 實現A模塊的接口,簡單示範一下,示例代碼以下:
public class AModuleImpl implements AModuleApi{
    public void testA() {
        System.out.println("如今在A模塊裏面操做testA方法");
    }
}
  1. 同理定義和實現B模塊、C模塊,先看B模塊的接口定義,示例代碼以下:
public interface BModuleApi {
    public void testB();
}

public class BModuleImpl implements BModuleApi{
    public void testB() {
        System.out.println("如今在B模塊裏面操做testB方法");
    }
}

public interface CModuleApi {
    public void testC();
}

public class CModuleImpl implements CModuleApi{
    public void testC() {
        System.out.println("如今在C模塊裏面操做testC方法");
    }
}
  1. 定義外觀對象,示例代碼以下:
/**
 * 外觀對象
 */
public class Facade {
    /**
     * 示意方法,知足客戶須要的功能
     */
    public void test(){
        //在內部實現的時候,可能會調用到內部的多個模塊
        AModuleApi a = new AModuleImpl();
        a.testA();
        BModuleApi b = new BModuleImpl();
        b.testB();
        CModuleApi c = new CModuleImpl();
        c.testC();
    }
}
  1. 客戶端如何使用呢,直接使用外觀對象就能夠了,示例代碼以下:
public class Client {
    public static void main(String[] args) {
        //使用Facade
        new Facade().test();
    }
}

##2.4 使用外觀模式重寫示例## 要使用外觀模式重寫前面的示例,其實很是簡單,只要添加一個Facade的對象,而後在裏面實現客戶端須要的功能就能夠了。

  1. 新添加一個Facade對象,示例代碼以下:
/**
 * 代碼生成子系統的外觀對象
 */
public class Facade {
    /**
     * 客戶端須要的,一個簡單的調用代碼生成的功能
     */
    public void generate(){
        new Presentation().generate();
        new Business().generate();
        new DAO().generate();
    }
}
  1. 其它的定義和實現都沒有變化,這裏就不去贅述了

  2. 看看此時的客戶端怎麼實現,再也不須要客戶端去調用子系統內部的多個模塊,直接使用外觀對象就能夠了,示例代碼以下:

public class Client {
    public static void main(String[] args) {
        //使用Facade
        new Facade().generate();
    }
}

如同上面講述的例子,Facade類其實至關於A、B、C模塊的外觀界面,Facade類也被稱爲A、B、C模塊對外的接口,有了這個Facade類,那麼客戶端就不須要知道系統內部的實現細節,甚至客戶端都不須要知道A、B、C模塊的存在,客戶端只須要跟Facade類交互就行了,從而更好的實現了客戶端和子系統中A、B、C模塊的解耦,讓客戶端更容易的使用系統。

#3 模式講解# ##3.1 認識外觀模式##

  1. 外觀模式的目的

外觀模式的目的不是給子系統添加新的功能接口,而是爲了讓外部減小與子系統內多個模塊的交互,鬆散耦合,從而讓外部可以更簡單的使用子系統。

這點要特別注意,由於外觀是看成子系統對外的接口出現的,雖然也能夠在這裏定義一些子系統沒有的功能,但不建議這麼作。外觀應該是包裝已有的功能,它主要負責組合已有功能來實現客戶須要,而不是添加新的實現

  1. 使用外觀跟不使用相比有何變化

看到Facade的實現,可能有些朋友會說,這不就是把原來在客戶端的代碼搬到Facade裏面了嗎?沒有什麼大變化啊?

沒錯,說的很對,表面上看就是把客戶端的代碼搬到Facade裏面了,但實質是發生了變化的,請思考:Facade到底位於何處呢?是位於客戶端仍是在由A、B、C模塊組成的系統這邊呢?

答案確定是在系統這邊,這有什麼不同嗎?

固然有了,若是Facade在系統這邊,那麼它就至關於屏蔽了外部客戶端和系統內部模塊的交互,從而把A、B、C模塊組合成爲一個總體對外,不但方便了客戶端的調用,並且封裝了系統內部的細節功能,也就是說Facade與各個模塊交互的過程已是內部實現了。這樣一來,若是從此調用模塊的算法發生了變化,好比變化成要先調用B,而後調用A,那麼只須要修改Facade的實現就能夠了。

另一個好處,Facade的功能能夠被不少個客戶端調用,也就是說Facade能夠實現功能的共享,也就是實現複用。一樣的調用代碼就只用在Facade裏面寫一次就行了,而不用在多個調用的地方重複寫。

還有一個潛在的好處,對使用Facade的人員來講,Facade大大節省了他們的學習成本,他們只須要了解Facade便可,無需再深刻到子系統內部,去了解每一個模塊的細節,也不用和這多個模塊交互,從而使得開發簡單,學習也容易。

  1. 有外觀,可是能夠不使用

雖然有了外觀,若是有須要,外部仍是能夠繞開Facade,直接調用某個具體模塊的接口,這樣就能實現兼顧組合功能和細節功能。好比在客戶端就想要使用A模塊的功能,那麼就不須要使用Facade,能夠直接調用A模塊的接口。示例代碼以下:

public class Client {
    public static void main(String[] args) {
        AModuleApi a = new AModuleImpl();
        a.testA();
    }
}
  1. 外觀提供了缺省的功能實現

如今的系統是越作越大、愈來愈複雜,對軟件的要求也就更高。爲了提升系統的可重用性,一般會把一個大的系統分紅不少個子系統,再把一個子系統分紅不少更小的子系統,一直分下去,分到一個一個小的模塊,這樣一來,子系統的重用性會獲得增強,也更容易對子系統進行定製和使用。

可是這也帶來一個問題,若是用戶不須要對子系統進行定製,僅僅就是想要使用它們來完成必定的功能,那麼使用起來會比較麻煩,須要跟這多個模塊交互。

外觀對象就能夠爲用戶提供一個簡單的、缺省的實現,這個實現對大多數的用戶來講都是已經足夠了的。可是外觀並不限制那些須要更多定製功能的用戶,直接越過外觀去訪問內部的模塊的功能。

  1. 外觀模式的調用順序示意圖

輸入圖片說明

##3.2 外觀模式的實現##

  1. Facade的實現

對於一個子系統而言,外觀類不須要不少,一般能夠實現成爲一個單例

也能夠直接把外觀中的方法實現成爲靜態的方法,這樣就能夠不須要建立外觀對象的實例而直接就能夠調用,這種實現至關於把外觀類當成一個輔助工具類實現。簡要的示例代碼以下:

public class Facade {
    private Facade() {
           
    }
    public static void test(){
        AModuleApi a = new AModuleImpl();
        a.testA();
        BModuleApi b = new BModuleImpl();
        b.testB();
        CModuleApi c = new CModuleImpl();
        c.testC();
    }
}
  1. Facade能夠實現成爲interface

雖然Facade一般直接實現成爲類,可是也能夠把Facade實現成爲真正的interface,只是這樣會增長系統的複雜程度,由於這樣會須要一個Facade的實現,還須要一個來獲取Facade接口對象的工廠,此時結構如圖所示:

輸入圖片說明

  1. Facade實現成爲interface的附帶好處

若是把Facade實現成爲接口,還附帶一個功能,就是可以有選擇性的暴露接口方法,儘可能減小模塊對子系統外提供的接口方法

換句話說,一個模塊的接口裏面定義的方法能夠分紅兩部分,一部分是給子系統外部使用的,一部分是子系統內部的模塊間相互調用時使用的。有了Facade接口,那麼用於子系統內部的接口功能就不用暴露給子系統外部了。

好比,定義以下的A、B、C模塊的接口:

public interface AModuleApi {
    public void a1();
    public void a2();

    public void a3();
}

同理定義B、C模塊的接口:

public interface BModuleApi {
    //對子系統外部
    public void b1();
    //子系統內部使用
    public void b2();
    //子系統內部使用
    public void b3();
}
public interface CModuleApi {
    //對子系統外部
    public void c1();
    //子系統內部使用
    public void c2();
    //子系統內部使用
    public void c3();
}

定義好了各個模塊的接口,接下來定義Facade的接口:

public interface FacadeApi {
    public void a1();
    public void b1();
    public void c1();
    public void test();
}

這樣定義Facade的話,外部只須要有Facade接口,就再也不須要其它的接口了,這樣就能有效地屏蔽內部的細節,省得客戶端去調用A模塊的接口時,發現了一些不須要它知道的接口,這會形成「接口污染」

好比a二、a3方法就不須要讓客戶端知道,不然既暴露了內部的細節,又讓客戶端迷惑。對客戶端來講,他可能還要去思考a二、a3方法用來幹什麼呢?其實a二、a3方法是對內部模塊之間交互的,本來就不是對子系統外部的,因此乾脆就不要讓客戶端知道。

  1. Facade的方法實現

Facade的方法實現中,通常是負責把客戶端的請求轉發給子系統內部的各個模塊進行處理,Facade的方法自己並不進行功能的處理,Facade的方法的實現只是實現一個功能的組合調用

固然在Facade中實現一個邏輯處理也並沒有不可,可是不建議這樣作,這不是Facade的本意,也超出了Facade的邊界。

##3.3 外觀模式的優缺點##

  1. 鬆散耦合

外觀模式鬆散了客戶端與子系統的耦合關係,讓子系統內部的模塊能更容易擴展和維護。

  1. 簡單易用

外觀模式讓子系統更加易用,客戶端再也不須要了解子系統內部的實現,也不須要跟衆多子系統內部的模塊進行交互,只須要跟外觀交互就能夠了,至關於外觀類爲外部客戶端使用子系統提供了一站式服務。

  1. 更好的劃分訪問層次

經過合理使用Facade,能夠幫助咱們更好的劃分訪問的層次。有些方法是對系統外的,有些方法是系統內部使用的。把須要暴露給外部的功能集中到外觀中,這樣既方便客戶端使用,也很好的隱藏了內部的細節。

  1. 過多的或者是不太合理的Facade也容易讓人迷惑,究竟是調用Facade好呢,仍是直接調用模塊好。

##3.4 思考外觀模式##

  1. 外觀模式的本質

外觀模式的本質:封裝交互,簡化調用。

Facade封裝了子系統外部和子系統內多個模塊的交互過程,從而簡化外部的調用。經過外觀,子系統爲外部提供一些高層的接口,以方便它們的使用。

  1. 對設計原則的體現

外觀模式很好的體現了「最少知識原則」。

若是不使用外觀模式,客戶端一般須要和子系統內部的多個模塊交互,也就是說客戶端會有不少的朋友,客戶端和這些模塊之間都有依賴關係,任意一個模塊的變更均可能會引發客戶端的變更。

使用外觀模式事後,客戶端只須要和外觀類交互,也就是說客戶端只有外觀類這一個朋友,客戶端就不須要去關心子系統內部模塊的變更狀況了,客戶端只是和這個外觀類有依賴關係。

這樣一來,客戶端不但簡單,並且這個系統會更有彈性。當系統內部多個模塊發生變化的時候,這個變化能夠被這個外觀類吸取和消化,並不須要影響到客戶端,換句話說就是:能夠在不影響客戶端的狀況下,實現系統內部的維護和擴展

  1. 什麼時候選用外觀模式

若是你但願爲一個複雜的子系統提供一個簡單接口的時候,能夠考慮使用外觀模式,使用外觀對象來實現大部分客戶須要的功能,從而簡化客戶的使用;

若是想要讓客戶程序和抽象類的實現部分鬆散耦合,能夠考慮使用外觀模式,使用外觀對象來將這個子系統與它的客戶分離開來,從而提升子系統的獨立性和可移植性;

若是構建多層結構的系統,能夠考慮使用外觀模式,使用外觀對象做爲每層的入口,這樣能夠簡化層間調用,也能夠鬆散層次之間的依賴關係;

##3.5 相關模式##

  1. 外觀模式和中介者模式

這兩個模式很是相似,可是有本質的區別。

中介者模式主要用來封裝多個對象之間相互的交互,多用在系統內部的多個模塊之間;而外觀模式封裝的是單向的交互,是從客戶端訪問系統的調用,沒有從系統中來訪問客戶端的調用。

中介者模式的實現裏面,是須要實現具體的交互功能的;而外觀模式的實現裏面,通常是組合調用或是轉調內部實現的功能,一般外觀模式自己並不實現這些功能。

中介者模式的目的主要是鬆散多個模塊之間的耦合,把這些耦合關係所有放到中介者中去實現;而外觀模式的目的是簡化客戶端的調用,這點和中介者模式也不一樣。

  1. 外觀模式和單例模式

一般一個子系統只須要一個外觀實例,因此外觀模式能夠和單例模式組合使用,把Facade類實現成爲單例。固然,也能夠跟前面示例的那樣,把外觀類的構造方法私有化,而後把提供給客戶端的方法實現成爲靜態的。

  1. 外觀模式和抽象工廠模式

外觀模式的外觀類一般須要和系統內部的多個模塊交互,每一個模塊通常都有本身的接口,因此在外觀類的具體實現裏面,須要獲取這些接口,而後組合這些接口來完成客戶端的功能。

那麼怎麼獲取這些接口呢?就能夠和抽象工廠一塊兒使用,外觀類經過抽象工廠來獲取所須要的接口,而抽象工廠也能夠把模塊內部的實現對Facade進行屏蔽,也就是說Facade也僅僅只是知道它從模塊中獲取的它須要的功能,模塊內部的細節,Facade也不知道了。

相關文章
相關標籤/搜索