定義
- 又叫門面模式,提供了一個統一的接口,用來訪問系統中的一羣接口
- 外觀模式定義了一個高層的接口,讓子系統更容易使用
類型
結構型
使用場景
- 子系統越來越複雜,增加外觀模式提供簡單調用接口
- 構建多層系統結構,利用外觀對象作爲每層的入口,簡化層間調用
優點
- 簡化了調用過程,無須瞭解深入子系統,防止帶來風險
- 減少系統依賴、鬆散耦合
- 更好的劃分訪問層次
- 符合迪米特法則,即最少知道原則
缺點
- 增加子系統、擴展子系統行爲容易引入風險
- 不符合開閉原則
相關設計模式
-
外觀模式和中介者模式
本質上的區別,外觀模式關注的是外界和子系統的交互,而中介者模式關注的是子系統內部之間的交互 -
外觀模式和單例模式
-
外觀模式和抽象工廠模式
外觀類可以通過抽象工廠獲取子系統的實例,這樣子系統可以將內部對外觀類進行屏蔽
coding
迪米特原則是最少知道原則,外觀模式是迪米特法則的非常典型的例子,降低我們應用層client和子系統之間的耦合度。
我們看一下積分兌換禮物的功能代碼
積分禮物
/**
* 積分禮物
**/
public class PointGift {
private String name;
public PointGift(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
禮物兌換校驗子系統
/**
* 禮物兌換校驗子系統
**/
public class QualifyService {
public boolean isAvailable(PointGift pointGift){
System.out.println("校驗"+pointGift.getName()+" 積分資格通過,庫存通過");
return true;
}
}
積分支付的系統
/**
* 積分支付的系統
**/
public class PointPaymentService {
public boolean pay(PointGift pointGift){
//扣減積分
System.out.println("支付"+pointGift.getName()+" 積分成功");
return true;
}
}
物流系統
/**
* 物流系統
**/
public class ShippingService {
/**
* 返回物流單號
* @param pointGift
* @return
*/
public String shipGift(PointGift pointGift){
//物流系統的對接邏輯
System.out.println(pointGift.getName()+"進入物流系統");
String shippingOrderNo="666";
return shippingOrderNo;
}
}
現在三個子系統禮物兌換校驗子系統、積分支付的系統、物流系統都已經有了,我們應用層在對接的時候我要關注這3個子系統,要挨個進行交互,首先交互資格校驗、然後支付積分、最後調用物流系統運輸禮物。例如說說一旦服務拆分後,作爲rpc服務,對外提供這3個接口,要求對接者來實現這3個接口,同時還要保證他們的順序,包括怎麼使用都要寫一個說明,那其實對方只關心的是禮物兌換邏輯,這3個服務呢完全可以封裝到內部,對外只提供一個外觀類,對方不需要知道這麼多子系統,包括他們之間什麼順序,裏面什麼邏輯,這些子系統的邏輯都不需要知道。其實可以想想迪米特法則,對方不需要知道的都不知道,那外觀模式可以很好的體現迪米特法則,不該知道的就不知道。
我們現在需要應用層知道什麼呢?
禮物兌換的服務
/**
* 禮物兌換的服務
**/
public class GiftExchangeService {
//注入服務
private QualifyService qualifyService;
private PointPaymentService pointPaymentService;
private ShippingService shippingService;
//注入
public void setQualifyService(QualifyService qualifyService) {
this.qualifyService = qualifyService;
}
public void setPointPaymentService(PointPaymentService pointPaymentService) {
this.pointPaymentService = pointPaymentService;
}
public void setShippingService(ShippingService shippingService) {
this.shippingService = shippingService;
}
/**
* 禮物兌換
* @param pointGift
*/
public void giftExchange(PointGift pointGift){
if(qualifyService.isAvailable(pointGift)){
//資格校驗通過
if(pointPaymentService.pay(pointGift)){
//如果積分支付成功
String ShippingOrderNo= shippingService.shipGift(pointGift);
System.out.println("物流系統下單成功,訂單號是:"+ShippingOrderNo);
}
}
}
}
看下類圖
看到一個禮物兌換的外觀類對外提供一個giftExchange方法,下面有3個子系統,分別是資格校驗、物流、支付,並且是1:1的菱形關係,也即是組合關係,對外只和外觀類交互,所有的子系統是不需要關心的,這個圖就很好詮釋了外觀類的UML類圖
應用層
public class Test {
public static void main(String[] args) {
PointGift pointGift=new PointGift("T恤");
GiftExchangeService giftExchangeService=new GiftExchangeService();
//注入
giftExchangeService.setQualifyService(new QualifyService());
giftExchangeService.setPointPaymentService(new PointPaymentService());
giftExchangeService.setShippingService(new ShippingService());
giftExchangeService.giftExchange(pointGift);
}
}
運行結果
校驗T恤 積分資格通過,庫存通過
支付T恤 積分成功
T恤進入物流系統
物流系統下單成功,訂單號是:666
運行結果沒有問題,再看下現在的類圖
看類圖,Test是不需要關心子系統的,可是上圖中和子系統發生關係了,Test創建了子系統,那實際在使用的時候只要保證Test和外觀類這條黃線就是ok的,但是這裏面爲什麼還有線呢?因爲在Test裏我們通過new來創建了子系統,現在我們更符合實際一點,假設外觀類裏的子系統已經通過spring注入了
/**
* 禮物兌換的服務
**/
public class GiftExchangeService {
//注入服務
private QualifyService qualifyService=new QualifyService();
private PointPaymentService pointPaymentService=new PointPaymentService();
private ShippingService shippingService=new ShippingService();
/**
* 禮物兌換
* @param pointGift
*/
public void giftExchange(PointGift pointGift){
if(qualifyService.isAvailable(pointGift)){
//資格校驗通過
if(pointPaymentService.pay(pointGift)){
//如果積分支付成功
String ShippingOrderNo= shippingService.shipGift(pointGift);
System.out.println("物流系統下單成功,訂單號是:"+ShippingOrderNo);
}
}
}
}
應用層
public class Test {
public static void main(String[] args) {
PointGift pointGift=new PointGift("T恤");
GiftExchangeService giftExchangeService=new GiftExchangeService();
giftExchangeService.giftExchange(pointGift);
}
}
運行結果肯定還是一樣的,我們再看看uml類圖
看到Test和子系統的關係已經消失了,子系統只和外觀類通信,應用層只和外觀類通信。所以我們日常在開發工作的時候,也要注意我們應用層到底和沒和子系統發生關係,這裏也是檢驗外觀模式使用正確與否的一個因素。
上面的示例中,我們使用的是實體外觀類,在UML類圖中也看到,它完美的支持了迪米特法則,但是新增一個子系統的話,我們還要修改實體外觀類,從這個角度出發,這並不符合開閉原則,再擴展下,如果我們這個外觀類使用抽象外觀類的話,也就是說我們上面還有個接口外觀類,通過這個外觀類來實現上面的抽象外觀接口,這也是一個擴展的方式。但是具體我們是使用實體外觀類還是抽象外觀外觀類來實現業務場景呢?還是要看具體的業務場景要求,和對它擴展的一個預期,如果這個外觀類不需要經常變化,或者以後的業務擴展非常有限的話,我們直接使用實體外觀類也是可以的,可以減少複雜度;那如果預期每一兩週就要新增一個子系統,那我們就要使用抽象外觀類了。
例如說下面uml中有個抽象外觀類的實現類,在左邊又一個抽象外觀類的實現類,如果左邊的實現變化比較大的話,再通過應用層Test關聯左邊的框,即使用新的抽象外觀類的實現類,這樣右邊老的是不需要變化的,對於版本控制,例如說一個app不同的版本,裏面的邏輯可能是不一樣的,在版本控制上也可以找到使用的場景,打個比方我們可以通過app獲取具體使用的版本,然後調用對應版本的外觀類實現。
源碼解析
spring-jdbc中的JdbcUtils
closeConnection對Connection 做了封裝
public static void closeConnection(Connection con) {
if (con != null) {
try {
con.close();
}
catch (SQLException ex) {
logger.debug("Could not close JDBC Connection", ex);
}
catch (Throwable ex) {
// We don't trust the JDBC driver: It might throw RuntimeException or Error.
logger.debug("Unexpected exception on closing JDBC Connection", ex);
}
}
}
public static void closeStatement(Statement stmt) {
if (stmt != null) {
try {
stmt.close();
}
catch (SQLException ex) {
logger.trace("Could not close JDBC Statement", ex);
}
catch (Throwable ex) {
// We don't trust the JDBC driver: It might throw RuntimeException or Error.
logger.trace("Unexpected exception on closing JDBC Statement", ex);
}
}
}
public static void closeResultSet(ResultSet rs) {
if (rs != null) {
try {
rs.close();
}
catch (SQLException ex) {
logger.trace("Could not close JDBC ResultSet", ex);
}
catch (Throwable ex) {
// We don't trust the JDBC driver: It might throw RuntimeException or Error.
logger.trace("Unexpected exception on closing JDBC ResultSet", ex);
}
}
}
public static Object getResultSetValue(ResultSet rs, int index) throws SQLException {
。。。
public static Object extractDatabaseMetaData(DataSource dataSource, DatabaseMetaDataCallback action)
throws MetaDataAccessException {
。。。
mybatis中的configuration
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;
}
tomcat源碼中RequestFacade
看名字就知道是一個Request的外觀類,正是一個HttpServletRequest的外觀類
public class RequestFacade implements HttpServletRequest {
。。。
裏面封裝了很多方法,有像我們熟悉的getParameter等等。再來看看Request對象
package org.apache.catalina.connector;
public class Request
implements HttpServletRequest {
。。。
那Request和RequestFacade是什麼關係呢?它們兩個都實現了HttpServletRequest接口,並且Request裏有Facade,裏面操作都是通過Facade來操作的
/**
* The facade associated with this request.
*/
protected RequestFacade facade = null;
/**
* Return the <code>ServletRequest</code> for which this object
* is the facade. This method must be implemented by a subclass.
*/
public HttpServletRequest getRequest() {
if (facade == null) {
facade = new RequestFacade(this);
}
return facade;
}
tomcat中的ResponseFacade
跟RequestFacade一樣
tomcat中的StandardSessionFacade
是處理Session的外觀類
public class StandardSessionFacade
implements HttpSession {
tomcat還有很多都是使用外觀模式的,我們可以搜facade