對于開發(fā)人員來說靴拱,設(shè)計模式有時候就是一道坎褥芒,但是設(shè)計模式又非常有用嚼松,過了這道坎嫡良,它可以讓你水平提高一個檔次。而在android開發(fā)中献酗,必要的了解一些設(shè)計模式又是必須的寝受,因為設(shè)計模式在Android源碼中,可以說是無處不在罕偎。對于想系統(tǒng)的學(xué)習(xí)設(shè)計模式的同學(xué)很澄,這里推薦一本書,《大話設(shè)計模式》颜及。
Android常用設(shè)計模式系列:
面向?qū)ο蟮幕A(chǔ)特征
面向?qū)ο蟮脑O(shè)計原則
單例模式
模板模式
適配器模式
工廠模式
代理模式
原型模式
策略模式
Build模式
觀察者模式
裝飾者模式
中介模式
門面模式
一甩苛、門面模式
門面模式是對象的結(jié)構(gòu)模式,外部與一個子系統(tǒng)的通信必須通過一個統(tǒng)一的門面對象進(jìn)行俏站。門面模式提供一個高層次的接口讯蒲,使得子系統(tǒng)更易于使用。
醫(yī)院的例子
現(xiàn)代的軟件系統(tǒng)都是比較復(fù)雜的肄扎,設(shè)計師處理復(fù)雜系統(tǒng)的一個常見方法便是將其“分而治之”爱葵,把一個系統(tǒng)劃分為幾個較小的子系統(tǒng)。如果把醫(yī)院作為一個子系統(tǒng)反浓,按照部門職能萌丈,這個系統(tǒng)可以劃分為掛號、門診雷则、劃價辆雾、化驗、收費月劈、取藥等度迂。看病的病人要與這些部門打交道猜揪,就如同一個子系統(tǒng)的客戶端與一個子系統(tǒng)的各個類打交道一樣惭墓,不是一件容易的事情。
首先病人必須先掛號而姐,然后門診腊凶。如果醫(yī)生要求化驗,病人必須首先劃價拴念,然后繳費钧萍,才可以到化驗部門做化驗≌螅化驗后再回到門診室风瘦。
上圖描述的是病人在醫(yī)院里的體驗,圖中的方框代表醫(yī)院公般。
解決這種不便的方法便是引進(jìn)門面模式万搔,醫(yī)院可以設(shè)置一個接待員的位置胡桨,由接待員負(fù)責(zé)代為掛號、劃價瞬雹、繳費昧谊、取藥等。這個接待員就是門面模式的體現(xiàn)挖炬,病人只接觸接待員揽浙,由接待員與各個部門打交道状婶。
二意敛、門面模式的結(jié)構(gòu)
門面模式?jīng)]有一個一般化的類圖描述,最好的描述方法實際上就是以一個例子說明膛虫。
由于門面模式的結(jié)構(gòu)圖過于抽象草姻,因此把它稍稍具體點。假設(shè)子系統(tǒng)內(nèi)有三個模塊稍刀,分別是ModuleA撩独、ModuleB和ModuleC,它們分別有一個示例方法账月,那么此時示例的整體結(jié)構(gòu)圖如下:
在這個對象圖中综膀,出現(xiàn)了兩個角色:
● 門面(Facade)角色 :客戶端可以調(diào)用這個角色的方法。此角色知曉相關(guān)的(一個或者多個)子系統(tǒng)的功能和責(zé)任局齿。在正常情況下剧劝,本角色會將所有從客戶端發(fā)來的請求委派到相應(yīng)的子系統(tǒng)去。
● 子系統(tǒng)(SubSystem)角色 :可以同時有一個或者多個子系統(tǒng)抓歼。每個子系統(tǒng)都不是一個單獨的類讥此,而是一個類的集合(如上面的子系統(tǒng)就是由ModuleA、ModuleB谣妻、ModuleC三個類組合而成)萄喳。每個子系統(tǒng)都可以被客戶端直接調(diào)用,或者被門面角色調(diào)用蹋半。子系統(tǒng)并不知道門面的存在他巨,對于子系統(tǒng)而言,門面僅僅是另外一個客戶端而已减江。
也就是說闻蛀,本來應(yīng)該客戶端來跟各個子系統(tǒng)來接觸,但是有了門面模式之后您市,客戶端有什么操作只需要跟門面來打交道觉痛,告訴門面類,下面的事情就交給門面來協(xié)調(diào)組織子系統(tǒng)來做茵休。
使用門面模式:
不使用門面模式:
三薪棒、門面模式的實現(xiàn)
使用門面模式還有一個附帶的好處手蝎,就是能夠有選擇性地暴露方法。一個模塊中定義的方法可以分成兩部分俐芯,一部分是給子系統(tǒng)外部使用的棵介,一部分是子系統(tǒng)內(nèi)部模塊之間相互調(diào)用時使用的。有了Facade類吧史,那么用于子系統(tǒng)內(nèi)部模塊之間相互調(diào)用的方法就不用暴露給子系統(tǒng)外部了邮辽。
比如,定義如下A贸营、B吨述、C模塊。
public class Module { /** * 提供給子系統(tǒng)外部使用的方法 */
public void a1(){}; /** * 子系統(tǒng)內(nèi)部模塊之間相互調(diào)用時使用的方法 */
public void a2(){};
public void a3(){};
}
public class ModuleB { /** * 提供給子系統(tǒng)外部使用的方法 */
public void b1(){}; /** * 子系統(tǒng)內(nèi)部模塊之間相互調(diào)用時使用的方法 */
public void b2(){};
public void b3(){};
}
public class ModuleC { /** * 提供給子系統(tǒng)外部使用的方法 */
public void c1(){}; /** * 子系統(tǒng)內(nèi)部模塊之間相互調(diào)用時使用的方法 */
public void c2(){};
public void c3(){};
}
public class ModuleFacade {
ModuleA a = new ModuleA();
ModuleB b = new ModuleB();
ModuleC c = new ModuleC(); /** * 下面這些是A钞脂、B揣云、C模塊對子系統(tǒng)外部提供的方法 */
public void a1(){
a.a1();
}
public void b1(){
b.b1();
}
public void c1(){
c.c1();
}
}
這樣定義一個ModuleFacade類可以有效地屏蔽內(nèi)部的細(xì)節(jié),免得客戶端去調(diào)用Module類時冰啃,發(fā)現(xiàn)一些不需要它知道的方法邓夕。比如a2()和a3()方法就不需要讓客戶端知道,否則既暴露了內(nèi)部的細(xì)節(jié)阎毅,又讓客戶端迷惑焚刚。對客戶端來說,他可能還要去思考a2()扇调、a3()方法用來干什么呢矿咕?其實a2()和a3()方法是內(nèi)部模塊之間交互的,原本就不是對子系統(tǒng)外部的肃拜,所以干脆就不要讓客戶端知道痴腌。
初學(xué)者往往以為通過繼承一個門面類便可在子系統(tǒng)中加入新的行為,這是錯誤的燃领。門面模式的用意是為子系統(tǒng)提供一個集中化和簡化的溝通管道士聪,而不能向子系統(tǒng)加入新的行為。比如醫(yī)院中的接待員并不是醫(yī)護(hù)人員猛蔽,接待員并不能為病人提供醫(yī)療服務(wù)剥悟。
四、模式應(yīng)用講解
1曼库、門面模式在Tomcat中的使用
Tomcat中門面模式使用的很多区岗,因為Tomcat中有很多不同組件,每個組件要相互通信毁枯,但是又不能將自己內(nèi)部數(shù)據(jù)過多的暴露給其他組件慈缔。用門面模式隔離數(shù)據(jù)是個很好的方法。
下面是Request上使用的門面模式:
使用過Servlet的人都清楚种玛,除了要在web.xml做相應(yīng)的配置外藐鹤,還需繼承一個叫HttpServlet的抽象類瓤檐,并且重寫doGet與doPost方法(當(dāng)然只重寫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方法有兩個參數(shù)娱节,參數(shù)類型是接口HttpServletRequest與接口HttpServletResponse挠蛉,那么從Tomcat中傳遞過來的真實類型到底是什么呢?通過debug會發(fā)現(xiàn)肄满,在真正調(diào)用TestServlet類之前谴古,會經(jīng)過很多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對象中的很多方法都是內(nèi)部組件之間相互交互時使用的,比如setComet月趟、setRequestedSessionId等方法(這里就不一一列舉了)灯蝴。這些方法并不對外部公開,但是又必須設(shè)置為public孝宗,因為還需要跟內(nèi)部組件之間交互使用穷躁。最好的解決方法就是通過使用一個Facade類,將與內(nèi)部組件之間交互使用的方法屏蔽掉因妇,只提供給外部程序感興趣的方法问潭。
如果不使用Facade類,直接傳遞的是Request對象和Response對象婚被,那么熟悉容器內(nèi)部運作的程序員可以分別把ServletRequest和ServletResponse對象向下轉(zhuǎn)換為Request和Response狡忙,并調(diào)用它們的公共方法。比如擁有Request對象址芯,就可以調(diào)用setComet灾茁、setRequestedSessionId等方法,這會危害安全性谷炸。
2北专、Android中的門面模式
Context是最重要的一個類型。它封裝了很多重要的操作旬陡,比如startActivity()拓颓、sendBroadcast()等,幾乎是開發(fā)者對應(yīng)用操作的統(tǒng)一入口描孟。Context是一個抽象類驶睦,它只是定義了抽象接口腻格,真正的實現(xiàn)在ContextImpl類中。它就類似于門面類啥繁。
另外在Android里面我們往往會寫各種各樣的工具來菜职,這些工具類可以幫我們完成各種各樣的操作或者幫我們獲取到各種各樣的信息,它也相當(dāng)于一個門面類旗闽,使用了門面模式酬核,我們需要什么操作直接調(diào)用就可以,它屏蔽了底層的實現(xiàn)适室。
五嫡意、門面模式的優(yōu)缺點
優(yōu)點:
松散耦合
減少系統(tǒng)的相互依賴,上面也提到不使用門面模式捣辆,外界訪問直接深入到子系統(tǒng)內(nèi)部蔬螟,相互之間是一種強(qiáng)耦合關(guān)系這樣的強(qiáng)依賴是系統(tǒng)設(shè)計所不能接受的,門面模式的出現(xiàn)就很好地解決了該問題汽畴,所有的依賴都是對門面對象的依賴旧巾,與子系統(tǒng)無關(guān)。簡單易用
門面模式讓子系統(tǒng)更加易用忍些,客戶端不再需要了解子系統(tǒng)內(nèi)部的實現(xiàn)鲁猩,也不需要跟眾多子系統(tǒng)內(nèi)部的模塊進(jìn)行交互,只需要跟門面類交互就可以了罢坝。更好的劃分訪問層次
通過合理使用Facade廓握,可以幫助我們更好地劃分訪問的層次。有些方法是對系統(tǒng)外的嘁酿,有些方法是系統(tǒng)內(nèi)部使用的隙券。把需要暴露給外部的功能集中到門面中,這樣既方便客戶端使用闹司,也很好地隱藏了內(nèi)部的細(xì)節(jié)娱仔。提高安全性
訪問子系統(tǒng)的哪些業(yè)務(wù)就開通哪些邏輯,不需要把子系統(tǒng)的內(nèi)部方法直接暴露給外部調(diào)用开仰。
缺點
門面模式最大的缺點就是不符合開閉原則拟枚,對修改關(guān)閉,對擴(kuò)展開放众弓。
系統(tǒng)投產(chǎn)后發(fā)現(xiàn)問題唯一能做的一件事就是修改門面角色的代碼恩溅,這個風(fēng)險相當(dāng)大。
六 結(jié)語
門面模式是一個很好的封裝方法谓娃,一個子系統(tǒng)比較復(fù)雜時脚乡,比如算法或者業(yè)務(wù)比較復(fù)雜,就可以封裝出一個或多個門面出來,項目的結(jié)構(gòu)簡單奶稠,而且擴(kuò)展性非常好俯艰。還有,對于一個較大項目锌订,為了避免人員帶來的風(fēng)險竹握,也可以使用門面模式。
門面模式和 中介模式
1辆飘、從定義上啦辐,門面模式為復(fù)雜的子系統(tǒng)提供一個統(tǒng)一的訪問界面,它定義的是一個高層接口蜈项,該接口使得子系統(tǒng)更加容易使用芹关,避免外部模塊深入到子系統(tǒng)內(nèi)部而產(chǎn)生與子系統(tǒng)內(nèi)部細(xì)節(jié)耦合的問題。中介者模式使用一個中介對象來封裝一系列同事對象的交互行為紧卒,它使各對象之間不再顯式地引用侥衬,從而使其耦合松散,建立一個可擴(kuò)展的應(yīng)用架構(gòu)跑芳。
2轴总、從功能上,門面模式只是增加了一個門面聋亡,它對子系統(tǒng)來說沒有增加任何的功能肘习,子系統(tǒng)若脫離門面模式是完全可以獨立運行的际乘。而中介者模式則增加了業(yè)務(wù)功能坡倔,它把各個同事類中的原有耦合關(guān)系移植到了中介者,同事類不可能脫離中介者而獨立存在脖含。
3罪塔、從關(guān)系上,對門面模式來說养葵,子系統(tǒng)不知道有門面存在征堪,而對中介者來說,每個同事類都知道中介者存在关拒,因為要依靠中介者調(diào)和同事之間的關(guān)系佃蚜,它們對中介者非常了解。
4着绊、從封裝程度上谐算,門面模式是一種簡單的封裝,所有的請求處理都委托給子系統(tǒng)完成归露,而中介者模式則需要有一個中心洲脂,由中心協(xié)調(diào)同事類完成,并且中心本身也完成部分業(yè)務(wù)剧包,它屬于更進(jìn)一步的業(yè)務(wù)功能封裝恐锦。