1 門面模式的定義
門面模式:Facade Pattern坡贺,也叫做外觀模式。要求一個(gè)子系統(tǒng)的外部與其內(nèi)部的通信必須通過一個(gè)統(tǒng)一的對象進(jìn)行。門面模式提供一個(gè)高層次的接口,使得子系統(tǒng)更易于使用患膛。
門面模式是一種比較常用的封裝模式,注重“統(tǒng)一的對象”耻蛇,也就是提供一個(gè)訪問子系統(tǒng)的接口踪蹬,除了這個(gè)接口不允許有任何訪問子系統(tǒng)的行為發(fā)生驹溃。
門面模式的通用類圖:
門面對象,是外界訪問子系統(tǒng)內(nèi)部的唯一途徑延曙,不管子系統(tǒng)內(nèi)部多么雜亂無章,只要有門面對象亡哄,就可以簡單訪問子系統(tǒng)枝缔。
- Facade門面角色
客戶端可以調(diào)用門面角色的方法,門面角色知曉子系統(tǒng)的所有功能和職責(zé)蚊惯。一般情況下愿卸,本角色會(huì)將所有從客戶端發(fā)來的請求委派到相應(yīng)的子系統(tǒng)去,也就是說門面角色沒有實(shí)際的業(yè)務(wù)邏輯截型,只是一個(gè)委托類趴荸。 - Subsystem子系統(tǒng)角色
可以同時(shí)有一個(gè)或者多個(gè)子系統(tǒng),每一個(gè)子系統(tǒng)都不是一個(gè)單獨(dú)的類宦焦,而是一個(gè)類的集合发钝。子系統(tǒng)并不知道門面的存在,對于子系統(tǒng)而言波闹,門面僅僅是另外一個(gè)客戶端而已酝豪。
2 門面模式通用示例模式
2.1 子系統(tǒng)通用代碼
子系統(tǒng)是類的集合,并且每一個(gè)子系統(tǒng)都不相同精堕,我們使用3個(gè)相互無關(guān)的類來代表孵淘。可以認(rèn)為這3個(gè)類屬于近鄰歹篓,處理相關(guān)的業(yè)務(wù)瘫证,因此可以認(rèn)為是一個(gè)子系統(tǒng)的不同邏輯處理模塊,對于此子系統(tǒng)的訪問需要通過門面進(jìn)行庄撮。
- 業(yè)務(wù)類A
@Slf4j
public class SubsystemA {
/**
* 業(yè)務(wù)邏輯A
*/
public void doSomethingA() {
log.info("{}的業(yè)務(wù)邏輯背捌。", this.getClass().getSimpleName());
}
}
- 業(yè)務(wù)類B
@Slf4j
public class SubsystemB {
/**
* 業(yè)務(wù)邏輯B
*/
public void doSomethingB() {
log.info("{}的業(yè)務(wù)邏輯。", this.getClass().getSimpleName());
}
}
- 業(yè)務(wù)類C
@Slf4j
public class SubsystemC {
/**
* 業(yè)務(wù)邏輯C
*/
public void doSomethingC() {
log.info("{}的業(yè)務(wù)邏輯洞斯。", this.getClass().getSimpleName());
}
}
2.2 門面對象
@Slf4j
public class Facade {
//被委托的對象
private SubsystemA subsystemA = new SubsystemA();
private SubsystemB subsystemB = new SubsystemB();
private SubsystemC subsystemC = new SubsystemC();
/**
* 提供給外部訪問的方法
*/
public void businessA() {
this.subsystemA.doSomethingA();
}
public void businessB() {
this.subsystemB.doSomethingB();
}
public void businessC() {
this.subsystemC.doSomethingC();
}
}
2.3 場景類
@Slf4j
public class Client {
public static void main(String[] args) {
Facade facade = new Facade();
facade.businessA();
facade.businessB();
facade.businessC();
}
}
運(yùn)行結(jié)果:
21:20:14.042 [main] INFO com.idear.design.pattern.facade.SubsystemA - SubsystemA的業(yè)務(wù)邏輯载萌。
21:20:14.046 [main] INFO com.idear.design.pattern.facade.SubsystemB - SubsystemB的業(yè)務(wù)邏輯。
21:20:14.046 [main] INFO com.idear.design.pattern.facade.SubsystemC - SubsystemC的業(yè)務(wù)邏輯巡扇。
3 門面模式的優(yōu)缺點(diǎn)
3.1 優(yōu)點(diǎn)
- 減少系統(tǒng)的相互依賴
門面模式可以讓場景類只需要依賴門面對象扭仁,而與子系統(tǒng)無關(guān)。因此可以降低系統(tǒng)耦合厅翔。 - 提高靈活性
以來減少了乖坠,不管子系統(tǒng)內(nèi)部如何變化,只要不修改門面對象的對外接口就行刀闷,提高了靈活性熊泵。 - 提高安全性
外部只能通過門面訪問子系統(tǒng)的功能仰迁,門面沒有開放的就不能訪問,提高了子系統(tǒng)的安全性顽分。
3.2 缺點(diǎn)
門面模式最大的缺點(diǎn)是不符合開閉原則徐许。系統(tǒng)投產(chǎn)后,一旦發(fā)現(xiàn)錯(cuò)誤卒蘸,九比西藥修改門面角色的代碼雌隅,風(fēng)險(xiǎn)比較大。
4 門面模式的使用場景
4.1 使用場景
- 當(dāng)一個(gè)復(fù)雜的系統(tǒng)模塊或者子系統(tǒng)需要向外界提供一個(gè)訪問接口的時(shí)候缸沃;
- 子系統(tǒng)相對獨(dú)立——其他(子)系統(tǒng)對該系統(tǒng)的訪問只需要黑箱操作恰起,不需要關(guān)注內(nèi)部實(shí)現(xiàn)細(xì)節(jié);
- 預(yù)防低水平人員帶來的風(fēng)險(xiǎn)擴(kuò)散
為降低個(gè)人代碼質(zhì)量對整體項(xiàng)目的影響風(fēng)險(xiǎn)趾牧,一般指定相關(guān)人員在特定的子系統(tǒng)中進(jìn)行開發(fā)检盼,然后提供門面接口進(jìn)行訪問操作。
4.2 注意事項(xiàng)
- 一個(gè)子系統(tǒng)可以有多個(gè)門面
一般情況下翘单,一個(gè)子系統(tǒng)只要有一個(gè)門面就夠了吨枉。但是,當(dāng)以下情況可以有多個(gè):
- 門面已經(jīng)過于龐大繁雜
代碼行數(shù)太多哄芜,包含業(yè)務(wù)邏輯太多东羹。此時(shí)可以按照職責(zé)拆分為多個(gè)門面。例如忠烛,用戶信息的更新属提、創(chuàng)建、查詢分別提供門面美尸。 - 子系統(tǒng)可以提供不同的訪問路徑
比如示例代碼中冤议,可能業(yè)務(wù)模塊A需要Facade的所有模塊,但是業(yè)務(wù)模塊B只需要Facade的businessA()業(yè)務(wù)师坎。此時(shí)A就可以使用Facade恕酸,而單獨(dú)為業(yè)務(wù)模塊B提供一個(gè)只包含businessA()業(yè)務(wù)的Facade,提高安全性胯陋。
- 門面不參與子系統(tǒng)內(nèi)部的業(yè)務(wù)邏輯
以2中的代碼為例蕊温,如果業(yè)務(wù)businessC()必須先調(diào)用SubsystemA.doSomethingA()
,然后調(diào)用SubsystemC.doSomethingC()
遏乔。這時(shí)候义矛,可能很多時(shí)候都會(huì)按照如下進(jìn)行設(shè)計(jì):
錯(cuò)誤示例
public void businessC() {
this.subsystemA.doSomethingA();
this.subsystemC.doSomethingC();
}
這種設(shè)計(jì)很不靠譜,因?yàn)殚T面對象參與了邏輯盟萨。門面對象應(yīng)該只是提供訪問子系統(tǒng)的路徑凉翻,不應(yīng)該也不能參與具體的業(yè)務(wù)邏輯,否則就會(huì)產(chǎn)生依賴倒置的問題:子系統(tǒng)必須依賴門面才能被訪問捻激,同時(shí)違反了單一職責(zé)原則制轰,破壞了系統(tǒng)的封裝性前计。
那么如何解決呢?
建立一個(gè)封裝類垃杖,封裝完畢后提供給門面對象使用男杈。Context封裝類提供了一個(gè)聯(lián)合業(yè)務(wù)joinBusinessC()
,并且運(yùn)行在子系統(tǒng)內(nèi)部调俘。Context向門面對象提供joinBusinessC()
業(yè)務(wù)邏輯伶棒。
封裝對象Context:
@Slf4j
public class Context {
//被委托的對象
private SubsystemA subsystemA = new SubsystemA();
private SubsystemC subsystemC = new SubsystemC();
/**
* 聯(lián)合功能
*/
public void joinBusinessC() {
this.subsystemA.doSomethingA();
this.subsystemC.doSomethingC();
}
}
門面對象:
@Slf4j
public class FacadeJoin {
//被委托的對象
private SubsystemA subsystemA = new SubsystemA();
private SubsystemB subsystemB = new SubsystemB();
private Context context = new Context();
/**
* 提供給外部訪問的方法
*/
public void businessA() {
this.subsystemA.doSomethingA();
}
public void businessB() {
this.subsystemB.doSomethingB();
}
public void businessC() {
this.context.joinBusinessC();
}
}
通過封裝,業(yè)務(wù)只需要關(guān)注門面對象脉漏,具體的業(yè)務(wù)邏輯封裝在子系統(tǒng)內(nèi)部。即便有一天業(yè)務(wù)發(fā)生了變化袖牙,變化也會(huì)被封裝在子系統(tǒng)內(nèi)部侧巨,對于外部調(diào)用者來說,還是同一個(gè)門面鞭达,同樣的方法司忱,符合開閉原則和依賴倒置原則。