門面模式
門面模式(Facade Pattern)又叫外觀模式,提供了一個統(tǒng)一的接口,用來訪問子系統(tǒng)中的一群接口。其主要特征是定義了一個高層接口,讓子系統(tǒng)更容易使用扇丛,屬于結(jié)構(gòu)性模式。
原文: :Provide a unified interface to a set of interfaces in a subsystem.Facade defines a higher-level interface that makes the subsystem easier to use.
解釋:要求一個子系統(tǒng)的外部與其內(nèi)部的通信必須通過一個同一的對象進(jìn)行尉辑。門面模式提供一個高層次的接口帆精,使得子系統(tǒng)更易于使用。
其實(shí)隧魄,在我們?nèi)粘5木幋a工作中卓练,我們都在有意無意地大量使用門面模式,但凡只要高層模塊需要調(diào)度多個子系統(tǒng)(2 個以上類對象)购啄,我們都會自覺地創(chuàng)建一個新類封裝這些子系統(tǒng)襟企,提供精簡接口,讓高層模塊可以更加容易間接調(diào)用這些子系統(tǒng)的功能狮含。尤其是現(xiàn)階段各種第三方 SDK顽悼,各種開源類庫,很大概率都會使用門面模式几迄。尤其是你覺得調(diào)用越方便的蔚龙,門面模式使用的一般更多。
門面模式的應(yīng)用場景
1映胁、子系統(tǒng)越來越復(fù)雜木羹,增加門面模式提供簡單接口
2、構(gòu)建多層系統(tǒng)結(jié)構(gòu)屿愚,利用門面對象作為每層的入口汇跨,簡化層間調(diào)用务荆。
門面模式的通用寫法
門面模式的 UML 類圖:門面模式主要包含 2 種角色:
外觀角色(Facade):也稱門面角色妆距,系統(tǒng)對外的統(tǒng)一接口穷遂;
子系統(tǒng)角色(SubSystem):可以同時有一個或多個 SubSystem
。每個 SubSytem
都不是一個單獨(dú)的類娱据,而是一個類的集合蚪黑。SubSystem
并不知道 Facade
的存在,對于 SubSystem
而言中剩,Facade
只是另一個客戶端而已(即 Facade
對 SubSystem
透明)忌穿。
門面模式的通用代碼,首先分別創(chuàng)建 3 個子系統(tǒng)的業(yè)務(wù)邏輯 SubSystemA结啼、SubSystemB掠剑、SubSystemC,代碼很簡單:
public class SubSystemA {
public void doA() {
System.out.println("doing A stuff");
}
}
public class SubSystemB {
public void doB() {
System.out.println("doing B stuff");
}
}
public class SubSystemC {
public void doC() {
System.out.println("doing C stuff");
}
}
然后郊愧,創(chuàng)建外觀角色 Facade 類:
public class Facade {
private SubSystemA a = new SubSystemA();
private SubSystemB b = new SubSystemB();
private SubSystemC c = new SubSystemC();
// 對外接口
public void doA() {
this.a.doA();
}
// 對外接口
public void doB() {
this.b.doB();
}
// 對外接口
public void doC() {
this.c.doC();
}
}
來看客戶端代碼:
public static void main(String[] args) {
Facade facade = new Facade();
facade.doA();
facade.doB();
facade.doC();
}
門面模式業(yè)務(wù)場景實(shí)例
xx社區(qū)上線了一個積分兌換禮品的商城朴译,這禮品商城中的大部分功能并不是全部重新開發(fā)的,而是要去對接已有的各個子系統(tǒng)
這些子系統(tǒng)可能涉及到積分系統(tǒng)属铁、支付系統(tǒng)眠寿、物流系統(tǒng)的接口調(diào)用。如果所有的接口調(diào)用全部由前端發(fā)送網(wǎng)絡(luò)請求去調(diào)用現(xiàn)有接口的話焦蘑,一則會增加前端開發(fā)人員的難度盯拱,二則會增加一些網(wǎng)絡(luò)請求影響頁面性能。這個時候就可以發(fā)揮門面模式的優(yōu)勢了例嘱。將所有現(xiàn)成的接口全部整合到一個類中狡逢,由后端提供統(tǒng)一的接口給前端調(diào)用,這樣前端開發(fā)人員就不需要關(guān)心各接口的業(yè)務(wù)關(guān)系拼卵,只需要把精力集中在頁面交互上甚侣。下面我們用代碼來模擬一下這個場景。
首先间学,創(chuàng)建禮品的實(shí)體類 GiftInfo:
public class GiftInfo {
private String name;
public GiftInfo(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
然后殷费,編寫各個子系統(tǒng)的業(yè)務(wù)邏輯代碼,分別創(chuàng)建積分系統(tǒng) QualifyService
類:
public class QualifyService {
public boolean isAvailable(GiftInfo giftInfo) {
System.out.println("校驗(yàn)" + giftInfo.getName() + " 積分資格通過,庫存通過");
return true;
}
}
支付系統(tǒng) PaymentService
類:
public class PaymentService {
public boolean pay(GiftInfo pointsGift) {
//扣減積分
System.out.println("支付" + pointsGift.getName() + " 積分成功");
return true;
}
}
物流系統(tǒng)ShippingService
類:
public class ShippingService {
//發(fā)貨
public String delivery(GiftInfo giftInfo) {
//物流系統(tǒng)的對接邏輯
System.out.println(giftInfo.getName() + "進(jìn)入物流系統(tǒng)");
String shippingOrderNo = "666";
return shippingOrderNo;
}
}
然后創(chuàng)建外觀角色 GiftFacdeService
類低葫,對外只開放一個兌換禮物的 exchange()
方法详羡,在 exchange()
方法內(nèi)部整合 3 個子系統(tǒng)的所有功能。
public class GiftFacadeService {
private QualifyService qualifyService = new QualifyService();
private PaymentService pointsPaymentService = new PaymentService();
private ShippingService shippingService = new ShippingService();
//兌換
public void exchange(GiftInfo giftInfo) {
if (qualifyService.isAvailable(giftInfo)) {
//資格校驗(yàn)通過
if (pointsPaymentService.pay(giftInfo)) {
//如果支付積分成功
String shippingOrderNo = shippingService.delivery(giftInfo);
System.out.println("物流系統(tǒng)下單成功,訂單號是:" + shippingOrderNo);
}
}
}
}
最后嘿悬,來看客戶端代碼:
public static void main(String[] args) {
GiftInfo giftInfo = new GiftInfo("《Spring Boot 編程思想》");
GiftFacadeService giftFacadeService = new GiftFacadeService();
giftFacadeService.exchange(giftInfo);
}
門面模式在源碼中的應(yīng)用
Spring JDBC 模塊下的 JdbcUtils 類实柠,它封裝了和JDBC 相關(guān)的所有操作,它一個代碼片段:
public abstract class JdbcUtils {
public static final int TYPE_UNKNOWN = -2147483648;
private static final Log logger = LogFactory.getLog(JdbcUtils.class);
public JdbcUtils() {}
public static void closeConnection(Connection con) {
if (con != null) {
try {
con.close();
} catch (SQLException var2) {
logger.debug("Could not close JDBC Connection", var2);
} catch (Throwable var3) {
logger.debug("Unexpected exception on closing JDBC Connection", var3);
}
}
}
public static void closeStatement(Statement stmt) {
if (stmt != null) {
try {
stmt.close();
} catch (SQLException var2) {
logger.trace("Could not close JDBC Statement", var2);
} catch (Throwable var3) {
logger.trace("Unexpected exception on closing JDBC Statement", var3);
}
}
}
public static void closeResultSet(ResultSet rs) {
if (rs != null) {
try {
rs.close();
} catch (SQLException var2) {
logger.trace("Could not close JDBC ResultSet", var2);
} catch (Throwable var3) {
logger.trace("Unexpected exception on closing JDBC ResultSet", var3);
}
}
}
...
}
其他更多的操作善涨,看它的結(jié)構(gòu)就非常清楚了:再來看一個 MyBatis 中的 Configuration 類窒盐。它其中有很多 new 開頭的方法草则,來看一下源代碼:
public MetaObject newMetaObject(Object object) {
return MetaObject.forObject(object, this.objectFactory, this.objectWrapperFactory, this.reflectorFactory);
}
public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement,
parameterObject, boundSql);
parameterHandler = (ParameterHandler) this.interceptorChain.pluginAll(parameterHandler);
return parameterHandler;
}
public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds,
ParameterHandler parameterHandler, ResultHandler resultHandler, BoundSql boundSql) {
ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler,
resultHandler, boundSql, rowBounds);
ResultSetHandler resultSetHandler = (ResultSetHandler) this.interceptorChain.pluginAll(resultSetHandler);
return resultSetHandler;
}
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject,
RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject,
rowBounds, resultHandler, boundSql);
StatementHandler statementHandler = (StatementHandler) this.interceptorChain.pluginAll(statementHandler);
return statementHandler;
}
public Executor newExecutor(Transaction transaction) {
return this.newExecutor(transaction, this.defaultExecutorType);
}
上面的這些方法都是對 JDBC 中關(guān)鍵組件操作的封裝。
另外地在 Tomcat 的源碼中也有體現(xiàn)蟹漓,也非常的有意思炕横。舉個例子 RequestFacade 類,來看源碼:
public class RequestFacade implements HttpServletRequest {
...
@Override
public String getContentType() {
if (request == null) {
throw new IllegalStateException(
sm.getString("requestFacade.nullRequest"));
}
return request.getContentType();
}
@Override
public ServletInputStream getInputStream() throws IOException {
if (request == null) {
throw new IllegalStateException(
sm.getString("requestFacade.nullRequest"));
}
return request.getInputStream();
}
@Override
public String getParameter(String name) {
if (request == null) {
throw new IllegalStateException(
sm.getString("requestFacade.nullRequest"));
}
if (Globals.IS_SECURITY_ENABLED) {
return AccessController.doPrivileged(
new GetParameterPrivilegedAction(name));
} else {
return request.getParameter(name);
}
}
...
}
看名字就知道它用了門面模式葡粒。它封裝了非常多的 request 的操作份殿,也整合了很多 servlet-api 以外的一些內(nèi)容,給用戶使用提供了很大便捷嗽交。同樣卿嘲,Tomcat 對 Response 和 Session 當(dāng)也封裝了ResponseFacade 和 StandardSessionFacade 類。
門面模式的優(yōu)缺點(diǎn)
優(yōu)點(diǎn):
1夫壁、簡化了調(diào)用過程拾枣,無需深入了解子系統(tǒng),以防給子系統(tǒng)帶來風(fēng)險盒让。
2梅肤、減少系統(tǒng)依賴、松散耦合
3糯彬、更好地劃分訪問層次凭语,提高了安全性
4、遵循迪米特法則撩扒,即最少知道原則似扔。
缺點(diǎn):
1、當(dāng)增加子系統(tǒng)和擴(kuò)展子系統(tǒng)行為時搓谆,可能容易帶來未知風(fēng)險
2炒辉、不符合開閉原則
3、某些情況下可能違背單一職責(zé)原則泉手。