定義
門(mén)面模式是對(duì)象的結(jié)構(gòu)模式九昧,外部與一個(gè)子系統(tǒng)的通信必須通過(guò)一個(gè)統(tǒng)一的門(mén)面對(duì)象進(jìn)行扰肌。門(mén)面模式提供一個(gè)高層次的接口能曾,使得子系統(tǒng)更易于使用。
醫(yī)院的例子
現(xiàn)在的軟件系統(tǒng)都是比較復(fù)雜的项滑,設(shè)計(jì)師處理復(fù)雜問(wèn)題的一個(gè)常見(jiàn)方法就是將其“分而治之”上炎,把一個(gè)系統(tǒng)拆分成幾個(gè)較小的子系統(tǒng)谨读。如果把一個(gè)醫(yī)院作為一個(gè)軟件系統(tǒng),按照部門(mén)職能晤锹,這個(gè)系統(tǒng)可以劃分為掛號(hào)摩幔、門(mén)診、劃價(jià)鞭铆、化驗(yàn)或衡、收費(fèi)、取藥等多個(gè)子系統(tǒng)车遂》舛希看病的病人需要同這些部門(mén)打交道,就如同一個(gè)系統(tǒng)的客戶(hù)端與系統(tǒng)的各個(gè)類(lèi)打交道一樣舶担,不是一件容易的事情坡疼。
首先病人必須先掛號(hào),然后門(mén)診衣陶。如果醫(yī)生要求化驗(yàn)柄瑰,則病人需要先劃價(jià),然后繳費(fèi)剪况,才能到化驗(yàn)部門(mén)進(jìn)行化驗(yàn)檢查教沾。拿到化驗(yàn)結(jié)果后,再回到門(mén)診室拯欧。
上圖描述的是病人在醫(yī)院里的體驗(yàn)详囤,圖中的方框代表醫(yī)院。
解決這種不變的方法就是引進(jìn)門(mén)面模式镐作,醫(yī)院可以設(shè)置一個(gè)接待員的位置藏姐,由接待員負(fù)責(zé)代為掛號(hào)、劃價(jià)该贾、繳費(fèi)羔杨、取藥等過(guò)程。這個(gè)接待員就是門(mén)面模式的體現(xiàn)杨蛋,病人只需要接觸接待員兜材,由接待員與各個(gè)部門(mén)打交道。
門(mén)面模式的結(jié)構(gòu)
門(mén)面模式?jīng)]有一個(gè)一般化的類(lèi)圖描述逞力,最好的描述方法實(shí)際上就是以一個(gè)例子說(shuō)明曙寡。
由于門(mén)面模式的結(jié)構(gòu)圖過(guò)于抽象,因此把它稍微具體一點(diǎn)寇荧。假設(shè)系統(tǒng)有三個(gè)模塊举庶,分別是ModuleA
、ModuleB
和ModuleC
揩抡,他們分別有一個(gè)示例方法户侥,那么此時(shí)示例的整體結(jié)構(gòu)圖如下:
在這個(gè)對(duì)象圖中,出現(xiàn)了兩個(gè)角色:
- 門(mén)面角色(Facade):客戶(hù)端可以調(diào)用這個(gè)角色的方法峦嗤。此角色知曉相關(guān)的(一個(gè)或多個(gè))子系統(tǒng)的功能和責(zé)任蕊唐。在正常情況下,本角色會(huì)將所有從客戶(hù)端發(fā)來(lái)的請(qǐng)求委派到相應(yīng)的子系統(tǒng)去烁设。
-
子系統(tǒng)角色(SubSystem):可以同時(shí)有一個(gè)或多個(gè)子系統(tǒng)替梨,每個(gè)子系統(tǒng)都不是一個(gè)單獨(dú)的類(lèi),而是一個(gè)類(lèi)的集合(例如上面的子系統(tǒng)由三個(gè)
ModuleA
装黑、ModuleB
和ModuleC
三個(gè)類(lèi)組成)耙替。每個(gè)子系統(tǒng)都可以被客戶(hù)端直接調(diào)用,或者被門(mén)面角色調(diào)用曹体。子系統(tǒng)并不知道門(mén)面的存在俗扇,對(duì)于子系統(tǒng)而言,門(mén)面僅僅是另外一個(gè)客戶(hù)端而已箕别。
示例代碼
子系統(tǒng)角色中的類(lèi)
public class ModuleA {
public void testA() {
System.out.println("調(diào)用了ModuleA中的testA方法铜幽。");
}
}
public class ModuleB {
public void testB() {
System.out.println("調(diào)用了ModuleB中的testB方法。");
}
}
public class ModuleC {
public void testC() {
System.out.println("調(diào)用了ModuleC中的testC方法串稀。");
}
}
門(mén)面角色類(lèi):
public class Facade {
public void test() {
ModuleA moduleA = new ModuleA();
moduleA.testA();
ModuleB moduleB = new ModuleB();
moduleB.testB();
ModuleC moduleC = new ModuleC();
moduleC.testC();
}
}
客戶(hù)端角色類(lèi)
public class Client {
public static void main(String[] args) {
Facade facade = new Facade();
facade.test();
}
}
Facade
類(lèi)其實(shí)相當(dāng)于A除抛、B、C模塊的外觀界面母截,有了這個(gè)Facade
類(lèi)到忽,那么客戶(hù)端就不需要親自調(diào)用子系統(tǒng)中的A、B、C模塊了喘漏,也不需要知道系統(tǒng)內(nèi)部的實(shí)現(xiàn)細(xì)節(jié)护蝶,甚至都不需要知道A、B翩迈、C模塊的存在持灰,客戶(hù)端只需要跟Facade
類(lèi)交互就好了,從而更好的實(shí)現(xiàn)了客戶(hù)端和子系統(tǒng)中A负饲、B堤魁、C模塊的解耦,讓客戶(hù)端更容易的使用系統(tǒng)返十。
門(mén)面模式的實(shí)現(xiàn)
使用門(mén)面模式還有一個(gè)附帶的好處妥泉,就是能夠有選擇性的暴露方法。一個(gè)模塊中定義的方法可以分成兩部分:一部分是給子系統(tǒng)外部使用的洞坑;一部分是子系統(tǒng)內(nèi)部模塊之間相互調(diào)用時(shí)使用的盲链。有了Facade
類(lèi),那么用于子系統(tǒng)內(nèi)部模塊之前相互調(diào)用的方法就不需要暴露給子系統(tǒng)外部了检诗。
例如匈仗,定義如下A、B逢慌、C模塊悠轩。
public class ModuleA {
/**
* 提供給子系統(tǒng)外部使用的方法
*/
public void a1() {}
/**
* 子系統(tǒng)內(nèi)部模塊之間相互調(diào)用時(shí)使用的方法
*/
public void a2() {}
public void a3() {}
}
public class ModuleB {
/**
* 提供給子系統(tǒng)外部使用的方法
*/
public void b1() {}
/**
* 子系統(tǒng)內(nèi)部模塊之間相互調(diào)用時(shí)使用的方法
*/
public void b2() {}
public void b3() {}
}
public class ModuleC {
/**
* 提供給子系統(tǒng)外部使用的方法
*/
public void c1() {}
/**
* 子系統(tǒng)內(nèi)部模塊之間相互調(diào)用時(shí)使用的方法
*/
public void c2() {}
public void c3() {}
}
模塊門(mén)面裝飾類(lèi)
public class ModuleFacade() {
ModuleA a = new ModuleA();
ModuleB b = new ModuleB();
ModuleC c = new ModuleC();
/**
* 下面這些是A、B攻泼、C模塊對(duì)子系統(tǒng)外部提供的方法
*/
public void a1() {
a.a1();
}
public void b1() {
b.b1();
}
public void c1() {
c.c1();
}
}
這樣定義一個(gè)ModuleFacade
類(lèi)可以有效的屏蔽內(nèi)部的細(xì)節(jié)火架,免得客戶(hù)端去調(diào)用Module
的相關(guān)類(lèi)時(shí),發(fā)現(xiàn)一些不需要它知道的方法忙菠,比如a2()
和a3()
方法就不需要讓客戶(hù)端知道何鸡,否則既暴露了內(nèi)部的細(xì)節(jié),又讓客戶(hù)端迷惑牛欢。對(duì)客戶(hù)端來(lái)說(shuō)骡男,他可能還需要去考慮a2()
和a3()
方法的作用。其實(shí)a2()
和a3()
方法是內(nèi)部模塊之間交互的傍睹,原本就不是對(duì)子系統(tǒng)外部的隔盛,所以干脆就不要讓客戶(hù)端知道。
一個(gè)系統(tǒng)可以有幾個(gè)門(mén)面類(lèi)
在門(mén)面模式中拾稳,通常只需要一個(gè)門(mén)面類(lèi)吮炕,并且此門(mén)面類(lèi)只有一個(gè)實(shí)例,換句話說(shuō)访得,它是一個(gè)單例類(lèi)龙亲。當(dāng)然這并不意味著在整個(gè)系統(tǒng)中只能有一個(gè)門(mén)面類(lèi),而僅僅是說(shuō)對(duì)于一個(gè)子系統(tǒng)只有一個(gè)門(mén)面類(lèi)■或者說(shuō)杜耙,如果一個(gè)系統(tǒng)有好幾個(gè)子系統(tǒng)的話,每個(gè)子系統(tǒng)都有一個(gè)門(mén)面類(lèi)迎膜,整個(gè)系統(tǒng)可以有數(shù)個(gè)門(mén)面類(lèi)泥技。
為子系統(tǒng)增加新行為
初學(xué)者往往認(rèn)為通過(guò)繼承一個(gè)門(mén)面類(lèi)便可以在子系統(tǒng)中加入新的行為浆兰,這是錯(cuò)誤的磕仅。門(mén)面模式的用意是為子系統(tǒng)提供一個(gè)集中化和簡(jiǎn)化的溝通管道,而不能向子系統(tǒng)中增加新的行為簸呈。比如醫(yī)院中的接待人員不是醫(yī)護(hù)人員榕订,接待員并不能為病人提供醫(yī)療服務(wù)。
門(mén)面模式的優(yōu)點(diǎn)
-
松散耦合
門(mén)面模式松散了客戶(hù)端與子系統(tǒng)的耦合關(guān)系蜕便,讓子系統(tǒng)內(nèi)部的模塊能更容易拓展和維護(hù)劫恒。 -
簡(jiǎn)單易用
門(mén)面模式讓子系統(tǒng)更加易用,客戶(hù)端不再需要了解子系統(tǒng)內(nèi)部的實(shí)現(xiàn)轿腺,也不需要跟眾多子系統(tǒng)內(nèi)部的模塊進(jìn)行交互两嘴,只需要跟門(mén)面類(lèi)交互即可。 -
更好的劃分訪問(wèn)層次
通過(guò)合理的使用門(mén)面模式Facade
族壳,可以幫助我們更好的劃分訪問(wèn)的層次憔辫。有些方法是對(duì)系統(tǒng)外部的,有些方法是對(duì)系統(tǒng)內(nèi)部使用的仿荆。把需要暴露給外部的功能集中到門(mén)面類(lèi)中贰您,這樣既方便客戶(hù)端使用,也很好的隱藏了內(nèi)部的細(xì)節(jié)拢操。
門(mén)面模式在Tomcat中的使用
Tomcat中門(mén)面模式使用的很多锦亦,因?yàn)門(mén)omcat中有很多不同組件,每個(gè)組件要相互通信令境,但是又不能將自己的內(nèi)部數(shù)據(jù)過(guò)多的暴露給其他組件杠园。用門(mén)面模式隔離數(shù)據(jù)是很好的方法。
下面是Request
上使用的門(mén)面模式:
使用過(guò)Servlet
的人都清楚舔庶,除了要在web.xml
做相應(yīng)的配置外抛蚁,還需要繼承一個(gè)叫HttpServlet
的抽象類(lèi),并且重寫(xiě)doGet
和doPost
方法(當(dāng)然只重寫(xiě)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
方法又兩個(gè)參數(shù)篮绿,參數(shù)類(lèi)型是接口HttpServletRequest
與接口HttpServletResponse
,那么從Tomcat中傳遞國(guó)來(lái)的真實(shí)類(lèi)型到底是什么呢吕漂?通過(guò)Debug會(huì)發(fā)現(xiàn)亲配,在真正調(diào)用TestServlet之前,會(huì)經(jīng)歷很多Tomcat中的方法。如下圖所示:
注意紅色方框圈中的類(lèi)吼虎,StandardWrapperValue
類(lèi)中的invoke
方法225行代碼如下:
filterChain.doFilter (request.getRequest(), response.getResponse());
在StandardWrapperValue
類(lèi)并沒(méi)有直接將Request
對(duì)象與Response
對(duì)象傳遞給ApplicationFilterChain
類(lèi)的doFilter()
方法犬钢,傳遞的是RequestFacade
與ResponseFacade
對(duì)象,為什么這么說(shuō)呢思灰,看一下request.getRequest()
和response.getResponse()
方法就真相大白了玷犹。
Request類(lèi)
public HttpServletRequest getRequest() {
if (facade == null) {
facade = new RequestFacade(this);
}
return facade;
}
Response類(lèi)
public HttpServletResponse getResponse() {
if (facade == null) {
facade = new ResponseFacade(this);
}
return (facade);
}
可以看到它們返回的都是各自的一個(gè)門(mén)面類(lèi),那么這樣做有什么好處呢洒疚?
Request
對(duì)象中的很多方法都是內(nèi)部組件之間相互交互時(shí)使用的歹颓,例如setComet
、setRequestedSessionId
等方法(這里不一一列舉)油湖。這些方法并不對(duì)外部公開(kāi)巍扛,但是又必須設(shè)置為public
因?yàn)檫€需要跟內(nèi)部組件之間交互使用。最好的解決方法就是通過(guò)使用一個(gè)Facade
類(lèi)乏德,將與內(nèi)部組件之間交互使用的方法屏蔽掉撤奸,只提供給外部程序感興趣的方法。
如果不使用Facade
類(lèi)喊括,直接傳遞的是Request
對(duì)象與Response
對(duì)象胧瓜,那么熟悉容器內(nèi)部運(yùn)作的程序員可以分別把ServletRequest
和ServletResponse
對(duì)象向下轉(zhuǎn)換為Request
和Response
,并調(diào)用它們的公共方法郑什,比如擁有Request
對(duì)象府喳,就可以調(diào)用setComet
、setRequestedSessionId
等方法蹦误,這樣會(huì)危害安全性劫拢。