每天學習一個設計模式(五):結構型之門面模式

来源:https://www.cnblogs.com/aohongzhu/archive/2020/05/22/12938733.html
-Advertisement-
Play Games

一、基本概念 門面模式(外觀模式)是對象的結構模式,外部與一個子系統的通信必須通過一個統一的門面對象進行。門面模式提供一個高層次的介面,使得子系統更易於使用。 二、通俗解釋 FACADE門面模式:我有一個專業的Nikon相機,我就喜歡自己手動調光圈、快門,這樣照出來的照片才專業,但MM可不懂這些,教 ...


一、基本概念

門面模式(外觀模式)是對象的結構模式,外部與一個子系統的通信必須通過一個統一的門面對象進行。門面模式提供一個高層次的介面,使得子系統更易於使用。

二、通俗解釋

FACADE門面模式:我有一個專業的Nikon相機,我就喜歡自己手動調光圈、快門,這樣照出來的照片才專業,但MM可不懂這些,教了半天也不會。幸好相機有Facade設計模式,把相機調整到自動檔,只要對準目標按快門就行了,一切由相機自動調整,這樣MM也可以用這個相機給我拍張照片了。 門面模式:外部與一個子系統的通信必須通過一個統一的門面對象進行。門面模式提供一個高層次的介面,使得子系統更易於使用。每一個子系統只有一個門面類,而且此門面類只有一個實例,也就是說它是一個單例模式。但整個系統可以有多個門面類。

醫院的例子

現代的軟體系統都是比較複雜的,設計師處理複雜系統的一個常見方法便是將其“分而治之”,把一個系統劃分為幾個較小的子系統。如果把醫院作為一個子系統,按照部門職能,這個系統可以劃分為掛號、門診、劃價、化驗、收費、取藥等。看病的病人要與這些部門打交道,就如同一個子系統的客戶端與一個子系統的各個類打交道一樣,不是一件容易的事情。

首先病人必須先掛號,然後門診。如果醫生要求化驗,病人必須首先劃價,然後繳費,才可以到化驗部門做化驗。化驗後再回到門診室。

                                             

上圖描述的是病人在醫院里的體驗,圖中的方框代表醫院。

解決這種不便的方法便是引進門面模式,醫院可以設置一個接待員的位置,由接待員負責代為掛號、劃價、繳費、取藥等。這個接待員就是門面模式的體現,病人只接觸接待員,由接待員與各個部門打交道。

                                          

三、門面模式詳解

1.門面模式的結構

門面模式沒有一個一般化的類圖描述,最好的描述方法實際上就是以一個例子說明。

                                         

由於門面模式的結構圖過於抽象,因此把它稍稍具體點。假設子系統內有三個模塊,分別是ModuleA、ModuleB和ModuleC,它們分別有一個示例方法,那麼此時示例的整體結構圖如下:

                                      

在這個對象圖中,出現了兩個角色:

 ● 門面(Facade)角色 :客戶端可以調用這個角色的方法。此角色知曉相關的(一個或者多個)子系統的功能和責任。在正常情況下,本角色會將所有從客戶端發來的請求委派到相應的子系統去。

 ● 子系統(SubSystem)角色 :可以同時有一個或者多個子系統。每個子系統都不是一個單獨的類,而是一個類的集合(如上面的子系統就是由ModuleA、ModuleB、ModuleC三個類組合而成)。每個子系統都可以被客戶端直接調用,或者被門面角色調用。子系統並不知道門面的存在,對於子系統而言,門面僅僅是另外一個客戶端而已。

子系統角色中的類:

public class ModuleA {
    //示意方法
    public void testA(){
        System.out.println("調用ModuleA中的testA方法");
    }
}
public class ModuleB {
    //示意方法
    public void testB(){
        System.out.println("調用ModuleB中的testB方法");
    }
}
public class ModuleC {
    //示意方法
    public void testC(){
        System.out.println("調用ModuleC中的testC方法");
    }
}

門面角色類:

public class Facade {
    //示意方法,滿足客戶端需要的功能
    public void test(){
        ModuleA a = new ModuleA();
        a.testA();
        ModuleB b = new ModuleB();
        b.testB();
        ModuleC c = new ModuleC();
        c.testC();
    }
}

客戶端角色類:

public class Client {

    public static void main(String[] args) {
        
        Facade facade = new Facade();
        facade.test();
    }

}

Facade類其實相當於A、B、C模塊的外觀界面,有了這個Facade類,那麼客戶端就不需要親自調用子系統中的A、B、C模塊了,也不需要知道系統內部的實現細節,甚至都不需要知道A、B、C模塊的存在,客戶端只需要跟Facade類交互就好了,從而更好地實現了客戶端和子系統中A、B、C模塊的解耦,讓客戶端更容易地使用系統。

2.門面模式的實現

使用門面模式還有一個附帶的好處,就是能夠有選擇性地暴露方法。一個模塊中定義的方法可以分成兩部分,一部分是給子系統外部使用的,一部分是子系統內部模塊之間相互調用時使用的。有了Facade類,那麼用於子系統內部模塊之間相互調用的方法就不用暴露給子系統外部了。

比如,定義如下A、B、C模塊。

public class ModuleA {
    /**
     * 提供給子系統外部使用的方法
     */
    public void a1(){};
    
    /**
     * 子系統內部模塊之間相互調用時使用的方法
     */
    public void a2(){};
    public void a3(){};
}
public class ModuleB {
    /**
     * 提供給子系統外部使用的方法
     */
    public void b1(){};
    
    /**
     * 子系統內部模塊之間相互調用時使用的方法
     */
    public void b2(){};
    public void b3(){};
}
public class ModuleC {
    /**
     * 提供給子系統外部使用的方法
     */
    public void c1(){};
    
    /**
     * 子系統內部模塊之間相互調用時使用的方法
     */
    public void c2(){};
    public void c3(){};
}

門面類:

public class ModuleFacade {
    
    ModuleA a = new ModuleA();
    ModuleB b = new ModuleB();
    ModuleC c = new ModuleC();
    /**
     * 下麵這些是A、B、C模塊對子系統外部提供的方法
     */
    public void a1(){
        a.a1();
    }
    public void b1(){
        b.b1();
    }
    public void c1(){
        c.c1();
    }
}

這樣定義一個ModuleFacade類可以有效地屏蔽內部的細節,免得客戶端去調用Module類時,發現一些不需要它知道的方法。比如a2()和a3()方法就不需要讓客戶端知道,否則既暴露了內部的細節,又讓客戶端迷惑。對客戶端來說,他可能還要去思考a2()、a3()方法用來乾什麼呢?其實a2()和a3()方法是內部模塊之間交互的,原本就不是對子系統外部的,所以乾脆就不要讓客戶端知道。

一個系統可以有幾個門面類

在門面模式中,通常只需要一個門面類,並且此門面類只有一個實例,換言之它是一個單例類。當然這並不意味著在整個系統里只有一個門面類,而僅僅是說對每一個子系統只有一個門面類。或者說,如果一個系統有好幾個子系統的話,每一個子系統都有一個門面類,整個系統可以有數個門面類。

為子系統增加新行為

初學者往往以為通過繼承一個門面類便可在子系統中加入新的行為,這是錯誤的。門面模式的用意是為子系統提供一個集中化和簡化的溝通管道,而不能向子系統加入新的行為。比如醫院中的接待員並不是醫護人員,接待員並不能為病人提供醫療服務。

3.門面模式的優點

門面模式的優點:

 ● 鬆散耦合

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

 ● 簡單易用

門面模式讓子系統更加易用,客戶端不再需要瞭解子系統內部的實現,也不需要跟眾多子系統內部的模塊進行交互,只需要跟門面類交互就可以了。

 ● 更好的劃分訪問層次

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

四、門面模式在Tomcat中的使用

Tomcat中門面模式使用的很多,因為Tomcat中有很多不同組件,每個組件要相互通信,但是又不能將自己內部數據過多的暴露給其他組件。用門面模式隔離數據是個很好的方法。

下麵是Request上使用的門面模式:

                                                                

使用過Servlet的人都清楚,除了要在web.xml做相應的配置外,還需繼承一個叫HttpServlet的抽象類,並且重寫doGet與doPost方法(當然只重寫service方法也是可以的)。

public class TestServlet extends HttpServlet {

    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        
        this.doPost(request, response);
            
    }

    public void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
            
        
    }

}

可以看出doGet與doPost方法有兩個參數,參數類型是介面HttpServletRequest與介面HttpServletResponse,那麼從Tomcat中傳遞過來的真實類型到底是什麼呢?通過debug會發現,在真正調用TestServlet類之前,會經過很多Tomcat中的方法。如下圖所示

                       

註意紅色方框圈中的類,StandardWrapperValue類中的invoke方法225行代碼如下:

 filterChain.doFilter(request.getRequest(), response.getResponse());

在StandardWrapperValue類中並沒有直接將Request對象與Response對象傳遞給ApplicationFilterChain類的doFilter方法,傳遞的是RequestFacade與ResponseFacade對象,為什麼這麼說呢,看一下request.getRequest()與response.getResponse()方法就真相大白了。

Request類

public HttpServletRequest getRequest() {
    if (facade == null) {
        facade = new RequestFacade(this);
    }
    return facade;
}

Response類

public HttpServletResponse getResponse() {
    if (facade == null) {
        facade = new ResponseFacade(this);
    }
    return (facade);
}

可以看到它們返回都是各自的一個門面類,那麼這樣做有什麼好處呢?

Request對象中的很多方法都是內部組件之間相互交互時使用的,比如setComet、setRequestedSessionId等方法(這裡就不一一列舉了)。這些方法並不對外部公開,但是又必須設置為public,因為還需要跟內部組件之間交互使用。最好的解決方法就是通過使用一個Facade類,將與內部組件之間交互使用的方法屏蔽掉,只提供給外部程式感興趣的方法。

如果不使用Facade類,直接傳遞的是Request對象和Response對象,那麼熟悉容器內部運作的程式員可以分別把ServletRequest和ServletResponse對象向下轉換為Request和Response,並調用它們的公共方法。比如擁有Request對象,就可以調用setComet、setRequestedSessionId等方法,這會危害安全性。


(引用:《JAVA與模式》之門面模式


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 隨著技術以如此快的速度發展,現在我們有必要選擇合適的工具來使用。每個軟體項目都有它需要滿足的多個需求和規範,因此為了滿足這些需求,選擇一種編程語言以允許您以有效的方式開發和管理項目非常重要。 由於有許多種編程語言和框架可供選擇,它們之間的比較已成為必然,因為你需要知道哪一個提供了最好的服務。當涉及到 ...
  • 什麼是NodeJS JS是腳本語言,腳本語言都需要一個解析器才能運行。對於寫在HTML頁面里的JS,瀏覽器充當瞭解析器的角色。而對於需要獨立運行的JS,NodeJS就是一個解析器。 每一種解析器都是一個運行環境,不但允許JS定義各種數據結構,進行各種計算,還允許JS使用運行環境提供的內置對象和方法做 ...
  • 之前一直採用VS進行各種前端後端的開發,隨著項目的需要,正逐步融合純前端的開發模式,開始主要選型為Vue + Element 進行BS前端的開發,後續會進一步整合Vue + AntDesign的界面套件,作為兩種不同界面框架的展現方式。採用Vue + Element 的前端開發和之前的開發模式需要有... ...
  • 從事web前端6年的工作,曾經是信息管理的一名應屆生,由於專業難找工作,掙錢少,當時選擇了轉行學前端開發技術,今天師兄就給大家講一下,作為應屆生,想學前端快點找工作應該如何去學。 對於畢業生來說,最要緊的事情就是快點找到工作。所以你學前端的時候就抓重點來學,因為很多東西,工作上用不到,所以學了也沒必 ...
  • 前提: (1) 相關博文地址: SpringBoot + Vue + ElementUI 實現後臺管理系統模板 -- 前端篇(一):搭建基本環境:https://www.cnblogs.com/l-y-h/p/12930895.html SpringBoot + Vue + ElementUI 實現 ...
  • 迭代器模式是一種使用頻率非常高的設計模式,迭代器用於對一個聚合對象進行遍歷。通過引入迭代器可以將數據的遍歷功能從聚合對象中分離出來,聚合對象只負責存儲數據,聚合對象只負責存儲數據,而遍曆數據由迭代器來完成。 模式動機 一個聚合對象,如一個列表(List)或者一個集合(Set),應該提供一種方法來讓別 ...
  • 代理的本質無論任何時候,只要談到設計模式,大腦中一定要蹦出這四個字“活學活用”。要想對某個事物做到活學活用,必須要對它足夠瞭解,甚至要剖析到本質才行。總是會有些人說,我幹嘛要知道原理,幹嘛要去看源碼?會用就行了。對於這種情況,我只有五個字相送,“你開心就好”。不可否認,認識一個陌生事物,大部分情況還 ...
  • 一、JML初探 ​ 作為一種形式化語言,可以約束 代碼中類和方法的狀態和行為形成規格,通過將一系列具體代碼實現抽象成明確的行為介面,可以形成一種契約式編程模式, 設計者無需考慮實際的數據結構與演算法,可以聚焦於程式的整體邏輯, 形式化語言的無二義性能讓實現者準確理解介面功能,根據問題需要選擇合適的實現 ...
一周排行
    -Advertisement-
    Play Games
  • 1. 說明 /* Performs operations on System.String instances that contain file or directory path information. These operations are performed in a cross-pla ...
  • 視頻地址:【WebApi+Vue3從0到1搭建《許可權管理系統》系列視頻:搭建JWT系統鑒權-嗶哩嗶哩】 https://b23.tv/R6cOcDO qq群:801913255 一、在appsettings.json中設置鑒權屬性 /*jwt鑒權*/ "JwtSetting": { "Issuer" ...
  • 引言 集成測試可在包含應用支持基礎結構(如資料庫、文件系統和網路)的級別上確保應用組件功能正常。 ASP.NET Core 通過將單元測試框架與測試 Web 主機和記憶體中測試伺服器結合使用來支持集成測試。 簡介 集成測試與單元測試相比,能夠在更廣泛的級別上評估應用的組件,確認多個組件一起工作以生成預 ...
  • 在.NET Emit編程中,我們探討了運算操作指令的重要性和應用。這些指令包括各種數學運算、位操作和比較操作,能夠在動態生成的代碼中實現對數據的處理和操作。通過這些指令,開發人員可以靈活地進行算術運算、邏輯運算和比較操作,從而實現各種複雜的演算法和邏輯......本篇之後,將進入第七部分:實戰項目 ...
  • 前言 多表頭表格是一個常見的業務需求,然而WPF中卻沒有預設實現這個功能,得益於WPF強大的控制項模板設計,我們可以通過修改控制項模板的方式自己實現它。 一、需求分析 下圖為一個典型的統計表格,統計1-12月的數據。 此時我們有一個需求,需要將月份按季度劃分,以便能夠直觀地看到季度統計數據,以下為該需求 ...
  • 如何將 ASP.NET Core MVC 項目的視圖分離到另一個項目 在當下這個年代 SPA 已是主流,人們早已忘記了 MVC 以及 Razor 的故事。但是在某些場景下 SSR 還是有意想不到效果。比如某些靜態頁面,比如追求首屏載入速度的時候。最近在項目中回歸傳統效果還是不錯。 有的時候我們希望將 ...
  • System.AggregateException: 發生一個或多個錯誤。 > Microsoft.WebTools.Shared.Exceptions.WebToolsException: 生成失敗。檢查輸出視窗瞭解更多詳細信息。 內部異常堆棧跟蹤的結尾 > (內部異常 #0) Microsoft ...
  • 引言 在上一章節我們實戰了在Asp.Net Core中的項目實戰,這一章節講解一下如何測試Asp.Net Core的中間件。 TestServer 還記得我們在集成測試中提供的TestServer嗎? TestServer 是由 Microsoft.AspNetCore.TestHost 包提供的。 ...
  • 在發現結果為真的WHEN子句時,CASE表達式的真假值判斷會終止,剩餘的WHEN子句會被忽略: CASE WHEN col_1 IN ('a', 'b') THEN '第一' WHEN col_1 IN ('a') THEN '第二' ELSE '其他' END 註意: 統一各分支返回的數據類型. ...
  • 在C#編程世界中,語法的精妙之處往往體現在那些看似微小卻極具影響力的符號與結構之中。其中,“_ =” 這一組合突然出現還真不知道什麼意思。本文將深入剖析“_ =” 的含義、工作原理及其在實際編程中的廣泛應用,揭示其作為C#語法奇兵的重要角色。 一、下劃線 _:神秘的棄元符號 下劃線 _ 在C#中並非 ...