12.外觀模式Facade

1.初識外觀模式

為子系統中的一組接口提供一個一致的界面,Facade模式定義了一個高層接口,這個接口使得這一子系統更加容易使用。


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

2.體會外觀模式

2.1 場景問題——代碼生成的應用

問題如果現在客戶端需要使用這個代碼生成工具來生成需要的基礎代碼,該如何實現呢?

2.2 不用模式的解決方案

存在的問題:
客戶端為了使用生成代碼的功能,需要與生成代碼子系統內部的多個模塊交互。

2.3 使用模式的解決方案

3.理解外觀模式

3.1 認識外觀模式

1.外觀模式的目的
外觀模式的目的不是給子系統添加新的功能接口,而是為了讓外部減少與子系統內多個模塊的交互,松散耦合,從而讓外部能夠更簡單的使用子系統。

2.使用外觀跟不使用相比有何變化
Facade方便了客戶端的調用、封裝了系統內部的細節功能、實現功能的共享和復用

3.有外觀,但是可以不使用

4.外觀提供了缺省的功能實現

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


3.2 外觀模式的實現

1.把外觀類當成一個輔助工具類實現

2:Facade可以實現成為interface


3.Facade實現成為interface的附帶好處能夠有選擇性的暴露接口方法,盡量減少模塊對子系統外提供的接口方法。

4.Facade的方法實現
Facade的方法實現中,一般是負責把客戶端的請求轉發給子系統內部的各個模塊進行處理,Facade的方法本身并不進行功能的處理,Facade的方法的實現只是實現一個功能的組合調用。

3.3 外觀模式的優缺點

  • 松散耦合
  • 簡單易用
  • 更好的劃分訪問層次
  • 過多的或者是不太合理的Facade也容易讓人迷惑

4.思考外觀模式

4.1 外觀模式的本質

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

4.2 對設計原則的體現

體現了“最少知識原則” (迪米特原則)。

4.3 何時選用

  • 1)如果你希望為一個復雜的子系統提供一個簡單接口的時候,可以考慮使用外觀模式,使用外觀對象來實現大部分客戶需要的功能,從而簡化客戶的使用
  • 2)如果想要讓客戶程序和抽象類的實現部分松散耦合,可以考慮使用外觀模式,使用外觀對象來將這個子系統與它的客戶分離開來,從而提高子系統的獨立性和可移植性
  • 3)如果構建多層結構的系統,可以考慮使用外觀模式,使用外觀對象作為每層的入口,這樣可以簡化層間調用,也可以松散層次之間的依賴關系

另一種總結:

  • 包裝多個復雜的子系統,提供一個簡單的接口
  • 重新包裝系統,隱藏不想暴露的接口

5.案例

5.1 Java 三層結構

用 Java 開發我們經常使用三層結構:

  • controller 控制器層
  • service 服務層
  • dao 數據訪問層

有時候業務很簡單,例如根據用戶ID或者用戶信息,Service 層這樣寫:

User getUserById(Integer id){
    return userDao.getUserById(id);
}

dao:

User getUserById(Integer id){
    //查詢數據庫
    User user = queryDB();
    return user;
}

Service 層直接調用了userDao 的 getUserById() , Service 本身并沒有執行什么額外的代碼,那么為什么不省去 Service 層呢?

其實三層結構也蘊含了外觀模式的思想在內。假如 Service 有一個轉賬方法:

public boolean transMoney(Integer user1,Integer user2,Float money){
    //用戶1加錢
    userDao.addMoney(user1,money);
    //用戶2扣錢
    userDao.decMoney(user2,money);
    //轉賬日志
    logDao.addLog(user1,user2,money);
}

作為調用方來說,并不想知道轉賬操作具體要調用哪些 Dao,一行代碼 transMoney() 就能搞定豈不是皆大歡喜。

因此 Service 是很有必要的,一般在業務系統中,Service 層的類不僅僅是簡單的調用 dao,而是作為外觀,給 Controller 提供了更方便好用的接口。

不過無論多復雜的系統,總會有 Service 直接調用 dao 的getUserById() 的情況 ,我們是否可以偷懶直接在 Controller 調用 Dao 呢?

理論上是沒問題的,但是強烈建議不要這么干,因為這樣會導致層侵入,三層結構的層級混亂。

除非你的業務真的簡單到極致,那么干脆直接舍棄 Service 層。只要你有Service 層,就請不要跨層調用。

5.2 Tomcat中的外觀模式

在做 Servlet 開發時,我們經常用的兩個對象就是HttpServletRequest 和 HttpServletResponse ,但我們拿到的這兩個對象其實是被 Tomcat 經過了外觀包裹的對象,那么 Tomcat 為什么要這么做呢?

首先我們先來了解一下HttpServletRequest ,通過源碼可以發現,HttpServletRequest 是一個接口,有兩個類 RequestFacade 和 Request 實現了 HttpServletRequest :



以Facade 命名的,毫無疑問是用了外觀模式,下面給出一部分源碼:

Request類,繼承 HttpServletRequest :

public class Request implements HttpServletRequest {

}

當我們需要使用 Request 對象時,Tomcat 傳給我們的其實并不是 Request對象,而是 RequestFacade

RequestFacade 類:

package org.apache.catalina.connector;

public class RequestFacade implements HttpServletRequest {

    protected Request request = null;

    public RequestFacade(Request request) {
        this.request = request;
    }

    public Object getAttribute(String name) {
        if (this.request == null) {
            throw new IllegalStateException(sm.getString("requestFacade.nullRequest"));
        } else {
            return this.request.getAttribute(name);
        }
    }


    public String getProtocol() {
        if (this.request == null) {
            throw new IllegalStateException(sm.getString("requestFacade.nullRequest"));
        } else {
            return this.request.getProtocol();
        }
    }

}

從上面 RequestFacade 源碼中可以看到,當調用 getAttribute() , getProtocol() 等方法時,其實還是調用了Request 對象的getAttribute() 方法。

既然如此,為什么要多次一舉弄個 RequestFacade 呢 ,其實是為了安全,Tomcat 不想把過多的方法暴露給別人。

Tomcat 內部有很多組件,組件之間經常需要通訊,有些方法不得不定義為Public,這樣才能被其他組件所調用。

但是有些方法只希望內部通訊用,并不想暴露給 Web 開發者,否則會有安全問題。所以定義一個外觀類,只實現想要暴露給外部的方法。

所以 Tomcat 要傳 Request 給我們的時候,其實是這么做的:

return new RequestFacade(request);

從這個案例中可以看出外觀模式不僅僅用于將復雜的接口包裝為一個簡單的接口,也可以用于隱藏一些不想暴露給別人的方法或接口。

參考

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容