8.門面模式
8.1.課程目標
1、掌握門面模式和裝飾器模式的特征和應用場景
2、理解裝飾器模式和代理模式的根本區別。
3、了解門面模式的優、缺點。
4、了解裝飾器模式的優、缺點。
8.2.內容定位
1、定位高級課程,不太適合接觸業務場景比較單一的人群。
2、深刻了解門面模式和裝飾器模式的應用場景。
8.3.門面模式定義
門面模式(Facade Pattern)又叫外觀模式,提供了一個統一的接口,用來訪問子系統中的一群接
口。其主要特征是定義了一個高層接口,讓子系統更容易使用,屬於結構性模式。
原文: 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.
解釋:要求一個子系統的外部與其內部的通信必須通過一個同一的對象進行。門面模式提供一個高層次的接口,使得 子系統更易於使用。
其實,在我們日常的編碼工作中,我們都在有意無意地大量使用門面模式,但凡只要高層模塊需要
調度多個子系統(2個以上類對象),我們都會自覺地創建一個新類封裝這些子系統,提供精簡接口,
讓高層模塊可以更加容易間接調用這些子系統的功能。尤其是現階段各種第三方SDK,各種開源類庫,
很大概率都會使用門面模式。尤其是你覺得調用越方便的,門面模式使用的一般更多。
8.4.門面模式的應用場景
1、子系統越來越復雜,增加門面模式提供簡單接口
2、構建多層系統結構,利用門面對象作為每層的入口,簡化層間調用。
8.5.門面模式的通用寫法
首先來看門面模式的UML類圖:

門面模式主要包含2種角色:
外觀角色(Facade):也稱門面角色,系統對外的統一接口;
子系統角色(SubSystem):可以同時有一個或多個 SubSystem。每個 SubSytem 都不是一個單獨
的類,而是一個類的集合。 SubSystem 並不知道 Facade 的存在,對於 SubSystem 而言, Facade 只
是另一個客戶端而已(即 Facade 對 SubSystem 透明)。
下面是門面模式的通用代碼,首先分別創建3個子系統的業務邏輯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");
}
}
來看客戶端代碼:
// 外觀角色 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();
}
}
8.6.門面模式業務場景實例
Gper社區上線了一個積分兌換禮品的商城,這禮品商城中的大部分功能並不是全部重新開發的,而是要去對接已有的各個子系統(如下圖所示):

這些子系統可能涉及到積分系統、支付系統、物流系統的接口調用。如果所有的接口調用全部由前
端發送網絡請求去調用現有接口的話,一則會增加前端開發人員的難度,二則會增加一些網絡請求影響
頁面性能。這個時候就可以發揮門面模式的優勢了。將所有現成的接口全部整合到一個類中,由后端提
供統一的接口給前端調用,這樣前端開發人員就不需要關心各接口的業務關系,只需要把精力集中在頁
面交互上。下面我們用代碼來模擬一下這個場景。
首先,創建禮品的實體類GiftInfo:
public class GiftInfo {
private String name;
public GiftInfo(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
然后,編寫各個子系統的業務邏輯代碼,分別創建積分系統QualifyService類:
public class QualifyService {
public boolean isAvailable(GiftInfo giftInfo){
System.out.println("校驗" +giftInfo.getName() + "積分通過,庫存通過。");
return true;
}
}
支付系統PaymentService類:
public class PaymentService {
public boolean pay(GiftInfo giftInfo) {
System.out.println("扣減" + giftInfo.getName() + " 積分成功");
return true;
}
}
物流系統ShippingService類:
public class ShippingService {
public String delivery(GiftInfo giftInfo){
System.out.println(giftInfo.getName() + "進入物流系統");
String shippingNo = "666";
return shippingNo;
}
}
然后創建外觀角色GiftFacdeService 類,對外只開放一個兌換禮物的exchange()方法,在 exchange() 方法內部整合3個子系統的所有功能。
public class FacadeService {
private QualifyService qualifyService = new QualifyService();
private PaymentService paymentService = new PaymentService();
private ShippingService shippingService = new ShippingService();
//兌換
public void exchange(GiftInfo giftInfo){
//資格校驗通過
if(qualifyService.isAvailable(giftInfo)){
//如果支付積分成功
if(paymentService.pay(giftInfo)){
String shippingNo = shippingService.delivery(giftInfo);
System.out.println("物流系統下單成功,物流單號是:" + shippingNo);
}
}
}
}
最后,來看客戶端代碼:
public class Test {
public static void main(String[] args) {
FacadeService facadeService = new FacadeService();
GiftInfo giftInfo = new GiftInfo("《Spring 5核心原理》");
facadeService.exchange(giftInfo);
}
}
運行結果如下:
校驗《Spring 5核心原理》積分通過,庫存通過。
扣減《Spring 5核心原理》 積分成功
《Spring 5核心原理》進入物流系統
物流系統下單成功,物流單號是:666
通過這樣一個案例對比之后,相信大家對門面模式的印象非常深刻了。
8.7.門面模式在源碼中的應用
下面我們來門面模式在源碼中的應用,先來看Spring JDBC模塊下的JdbcUtils類,它封裝了和
JDBC相關的所有操作,它一個代碼片段:
public abstract class JdbcUtils {
/**
* Constant that indicates an unknown (or unspecified) SQL type.
* @see java.sql.Types
*/
public static final int TYPE_UNKNOWN = Integer.MIN_VALUE;
private static final Log logger = LogFactory.getLog(JdbcUtils.class);
/**
* Close the given JDBC Connection and ignore any thrown exception.
* This is useful for typical finally blocks in manual JDBC code.
* @param con the JDBC Connection to close (may be {@code null})
*/
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);
}
}
}
/**
* Close the given JDBC Statement and ignore any thrown exception.
* This is useful for typical finally blocks in manual JDBC code.
* @param stmt the JDBC Statement to close (may be {@code null})
*/
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);
}
}
}
/**
* Close the given JDBC ResultSet and ignore any thrown exception.
* This is useful for typical finally blocks in manual JDBC code.
* @param rs the JDBC ResultSet to close (may be {@code null})
*/
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);
}
}
}
...
}
其他更多的操作,看它的結構就非常清楚了:

再來看一個MyBatis中的Configuration類。它其中有很多new開頭的方法,來看一下源代碼:
public MetaObject newMetaObject(Object object) {
return MetaObject.forObject(object, objectFactory, objectWrapperFactory, reflectorFactory);
}
public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
parameterHandler = (ParameterHandler) 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) 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) interceptorChain.pluginAll(statementHandler);
return statementHandler;
}
public Executor newExecutor(Transaction transaction) {
return newExecutor(transaction, defaultExecutorType);
}
上面的這些方法都是對JDBC中關鍵組件操作的封裝。另外地在Tomcat的源碼中也有體現,也非常的
有意思。舉個例子RequestFacade類,來看源碼:
@SuppressWarnings("deprecation")
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以
外的一些內容,給用戶使用提供了很大便捷。同樣,Tomcat對Response和Session當也封裝了
ResponseFacade和StandardSessionFacade類,感興趣的小伙伴可以去深入了解一下。
8.8.門面模式的優缺點
優點:
1、簡化了調用過程,無需深入了解子系統,以防給子系統帶來風險。
2、減少系統依賴、松散耦合
3、更好地划分訪問層次,提高了安全性
4、遵循迪米特法則,即最少知道原則。
缺點:
1、當增加子系統和擴展子系統行為時,可能容易帶來未知風險
2、不符合開閉原則
3、某些情況下可能違背單一職責原則。
9.裝飾器模式
9.1.裝飾器模式定義
裝飾器模式(Decorator Pattern),也稱為包裝模式(Wrapper Pattern)是指在不改變原有對象
的基礎之上,將功能附加到對象上,提供了比繼承更有彈性的替代方案(擴展原有對象的功能),屬於
結構型模式。
原文:Attach additional responsibilities to an object dynamically keeping the same interface. Decorators provide a flexible alternative to subclassing for extending functionality.
解釋:動態地給一個對象添加一些額外的職責。就增加功能來說,裝飾器模式相比生成子類更為靈活。
裝飾器模式的核心是功能擴展。使用裝飾器模式可以透明且動態地擴展類的功能。
裝飾器模式主要用於透明且動態地擴展類的功能。其實現原理為:讓裝飾器實現被包裝類(Concrete
Component)相同的接口(Component)(使得裝飾器與被擴展類類型一致),並在構造函數中傳入
該接口(Component)對象,然后就可以在接口需要實現的方法中在被包裝類對象的現有功能上添加
新功能了。而且由於裝飾器與被包裝類屬於同一類型(均為Component),且構造函數的參數為其實
現接口類(Component),因此裝飾器模式具備嵌套擴展功能,這樣我們就能使用裝飾器模式一層一
層的對最底層被包裝類進行功能擴展了。
首先看下裝飾器模式的通用UML類圖:
從 UML 類圖中,我們可以看到,裝飾器模式 主要包含四種角色:

抽象組件(Component):可以是一個接口或者抽象類,其充當被裝飾類的原始對象,規定了被
裝飾對象的行為;
具體組件(ConcreteComponent):實現/繼承Component的一個具體對象,也即被裝飾對象;
抽象裝飾器(Decorator):通用的裝飾ConcreteComponent的裝飾器,其內部必然有一個屬性
指向 Component抽象組件;其實現一般是一個抽象類,主要是為了讓其子類按照其構造形式傳入一
個 Component 抽象組件,這是強制的通用行為(當然,如果系統中裝飾邏輯單一,並不需要實現許
多裝飾器,那么我們可以直接省略該類,而直接實現一個具體裝飾器(ConcreteDecorator)即可);
具體裝飾器(ConcreteDecorator):Decorator 的具體實現類,理論上,每個 ConcreteDecorator
都擴展了Component對象的一種功能;
總結:裝飾器模式角色分配符合設計模式里氏替換原則,依賴倒置原則,從而使得其具備很強的擴展性,最終滿足開 閉原則。
9.2.裝飾器模式的應用場景
裝飾器模式在我們生活中應用也比較多如給煎餅加雞蛋;給蛋糕加上一些水果;給房子裝修等,為對象擴展一些額外的職責。裝飾器在代碼程序中適用於以下場景:
1、用於擴展一個類的功能或給一個類添加附加職責。
2、動態的給一個對象添加功能,這些功能可以再動態的撤銷。
3、需要為一批的兄弟類進行改裝或加裝功能。
來看一個這樣的場景,上班族白領其實大多有睡懶覺的習慣,每天早上上班都是踩點,於是很多小
伙伴為了多賴一會兒床都不吃早餐。那么,也有些小伙伴可能在上班路上碰到賣煎餅的路邊攤,都會順
帶一個到公司茶水間吃早餐。賣煎餅的大姐可以給你的煎餅加雞蛋,也可以加香腸(如下圖,PS:我買
煎餅一般都要求不加生菜)。

下面我們用代碼還原一下碼農的生活。首先創建一個煎餅Battercake類:
public class Battercake {
protected String getMsg(){ return "煎餅";}
public int getPrice(){ return 5;}
}
創建一個加雞蛋的煎餅BattercakeWithEgg類:
public class BattercakeWithEgg extends Battercake {
@Override
protected String getMsg(){ return super.getMsg() + "+1個雞蛋";}
@Override
//加一個雞蛋加 1 塊錢
public int getPrice(){ return super.getPrice() + 1;}
}
再創建一個既加雞蛋又加香腸的BattercakeWithEggAndSausage類:
public class BattercakeWithEggAndSauage extends BattercakeWithEgg {
@Override
protected String getMsg(){ return super.getMsg() + "+1根香腸";}
@Override
//加一個香腸加 2 塊錢
public int getPrice(){ return super.getPrice() + 2;}
}
編寫客戶端測試代碼:
public class Test {
public static void main(String[] args) {
Battercake battercake = new Battercake();
System.out.println(battercake.getMsg() + ",總價:" + battercake.getPrice());
BattercakeWithEgg battercakeWithEgg = new BattercakeWithEgg();
System.out.println(battercakeWithEgg.getMsg() + ",總價:" + battercakeWithEgg.getPrice());
BattercakeWithEggAndSauage battercakeWithEggAndSauage = new BattercakeWithEggAndSauage();
System.out.println(battercakeWithEggAndSauage.getMsg() + ",總價:" + battercakeWithEggAndSauage.getPrice());
}
}
運行結果:
煎餅,總價:5
煎餅+1個雞蛋,總價:6
煎餅+1個雞蛋+1根香腸,總價:8
運行結果沒有問題。
但是,如果用戶需要一個加2個雞蛋加1根香腸的煎餅,那么用我們現在的類
結構是創建不出來的,也無法自動計算出價格,除非再創建一個類做定制。如果需求再變,一直加定制
顯然是不科學的。那么下面我們就用裝飾器模式來解決上面的問題。
首先創建一個建煎餅的抽象
Battercake類:
public abstract class Battercake {
protected abstract String getMsg();
protected abstract int getPrice();
}
創建一個基本的煎餅(或者叫基礎套餐)BaseBattercake:
public class BaseBattercake extends Battercake{
protected String getMsg(){ return "煎餅";}
public int getPrice(){ return 5;}
}
然后,再創建一個擴展套餐的抽象裝飾器BattercakeDecotator類:
public class BattercakeDecorator extends Battercake{
//靜態代理,委派
private Battercake battercake;
public BattercakeDecorator(Battercake battercake) {
this.battercake = battercake;
}
@Override
protected String getMsg(){ return this.battercake.getMsg();}
@Override
public int getPrice(){ return this.battercake.getPrice();}
}
然后,創建雞蛋裝飾器EggDecorator類:
public class EggDecorator extends BattercakeDecorator{
public EggDecorator(Battercake battercake) {
super(battercake);
}
@Override
protected String getMsg(){ return super.getMsg() + "1個雞蛋";}
@Override
public int getPrice(){ return super.getPrice() + 1;}
}
創建香腸裝飾器SausageDecorator類:
public class SauageDecorator extends BattercakeDecorator{
public SauageDecorator(Battercake battercake) {
super(battercake);
}
protected String getMsg(){ return super.getMsg() + "1根香腸";}
public int getPrice(){ return super.getPrice() + 2;}
}
編寫客戶端測試代碼:
public class Test {
public static void main(String[] args) {
//路邊攤買一個煎餅
Battercake battercake = new BaseBattercake();
//煎餅有點小,想再加一個雞蛋
battercake = new EggDecorator(battercake);
//再加一個雞蛋
battercake = new EggDecorator(battercake);
//很餓,再加根香腸
battercake = new SauageDecorator(battercake);
//跟靜態代理最大區別就是職責不同
//靜態代理不一定要滿足 is-a 的關系
//靜態代理會做功能增強,同一個職責變得不一樣
//裝飾器更多考慮是擴展
System.out.println(battercake.getMsg() + ",總價" + battercake.getPrice());
}
}
運行結果:
煎餅,總價:5
煎餅+1個雞蛋,總價:6
煎餅+1個雞蛋+1根香腸,總價:8
來看一下類圖:

為了加深印象,我們再來看一個應用場景。需求大致是這樣,系統采用的是sls服務監控項目日志,
以Json的格式解析,所以需要將項目中的日志封裝成json格式再打印。現有的日志體系采用了log4j+
slf4j框架搭建而成。客戶端調用是這樣的:
private static final Logger logger = LoggerFactory.getLogger(Component.class);
logger.error(string);
這樣打印出來的是毫無規則的一行行字符串。在考慮將其轉換成json格式時,我采用了裝飾器模式。
目前有的是統一接口 Logger 和其具體實現類,我要加的就是一個裝飾類和真正封裝成 Json格式的裝
飾產品類。創建裝飾器類LoggerDecorator:
public class LoggerDecorator implements Logger {
protected Logger logger;
public LoggerDecorator(Logger logger) {
this.logger = logger;
}
public void error(String s) {
}
public void error(String s, Object o) {
}
//省略其他默認實現
}
創建具體組件JasonLogger類實現代碼如下:
public class JsonLogger extends LoggerDecorator {
public JsonLogger(Logger logger) {
super(logger);
}
@Override
public void info(String s) {
JSONObject result = newJsonObject();
result.put("message",s);
logger.info(result.toString());
}
@Override
public void error(String s) {
JSONObject result = newJsonObject();
result.put("message",s);
logger.info(result.toString());
}
public void error(Exception e){
JSONObject result = newJsonObject();
result.put("exception",e.getClass().getName());
String trace = Arrays.toString(e.getStackTrace());
result.put("starckTrace",trace);
logger.info(result.toString());
}
private JSONObject newJsonObject(){
//拼裝了一些運行時信息
return new JSONObject();
}
}
可以看到,在JsonLogger中,對於Logger的各種接口,我都用JsonObject對象進行一層封裝。
在打印的時候,最終還是調用原生接口 logger.error(string),只是這個 string 參數已經被我們裝飾過
了。如果有額外的需求,我們也可以再寫一個函數去實現。比如 error(Exception e),只傳入一個異常
對象,這樣在調用時就非常方便了。
另外,為了在新老交替的過程中盡量不改變太多的代碼和使用方式。我又在JsonLogger 中加入了
一個內部的工廠類JsonLoggerFactory(這個類轉移到DecoratorLogger 中可能更好一些),他包含
一個靜態方法,用於提供對應的JsonLogger實例。最終在新的日志體系中,使用方式如下:
public class Test {
private static final Logger logger = JsonLoggerFactory.getLogger(Test.class);
public static void main(String[] args) {
logger.error("系統錯誤");
}
}
對於客戶端而言,唯一與原先不同的地方就是將 LoggerFactory 改為JsonLoggerFactory 即可,
這樣的實現,也會被更快更方便的被其他開發者接受和習慣。最后看一下類圖:

裝飾器模式最本質的特征是講原有類的附加功能抽離出來,簡化原有類的邏輯。通過這樣兩個案例,我們可以總結出來,其實抽象的裝飾器是可有可無的,具體可以根據業務模型來選擇。
9.3.裝飾器模式在源碼中的應用
裝飾器模式在源碼中也應用得非常多,在 JDK 中體現最明顯的類就是 IO 相關的類,如
BufferedReader、InputStream、OutputStream,看一下常用的InputStream的類結構圖:

在Spring 中的 TransactionAwareCacheDecorator類我們也可以來嘗試理解一下,這個類主要是用來處理事務緩存的,來看一下代碼:
public class TransactionAwareCacheDecorator implements Cache {
private final Cache targetCache;
public TransactionAwareCacheDecorator(Cache targetCache) {
Assert.notNull(targetCache, "Target Cache must not be null");
this.targetCache = targetCache;
}
public Cache getTargetCache() {
return this.targetCache;
}
...
}
TransactionAwareCacheDecorator就是對Cache的一個包裝。再來看一個 MVC中的裝飾器模式
HttpHeadResponseDecorator類:
public class HttpHeadResponseDecorator extends ServerHttpResponseDecorator {
public HttpHeadResponseDecorator(ServerHttpResponse delegate) {
super(delegate);
}
...
}
最后,看看 MyBatis中的一段處理緩存的設計 org.apache.ibatis.cache.Cache 類,找到它的包定
位:
從名字上來看其實更容易理解了。比如 FifoCache先入先出算法的緩存;LruCache最近最少使用
的緩存;TransactionlCache事務相關的緩存,都是采用裝飾器模式。MyBatis源碼在我們后續的課程
也會深入講解,感興趣的小伙伴可以詳細看看這塊的源碼,也可以好好學習一下MyBatis的命名方式,
今天我們還是把重點放到設計模式上。
9.4.裝飾器模式和代理模式對比
從代理模式的 UML類圖和通用代碼實現上看,代理模式與裝飾器模式幾乎一模一樣。代理模式的
Subject 對 應 裝 飾 器 模 式 的 Component , 代 理 模 式 的 RealSubject 對 應 裝 飾 器 模 式 的
ConcreteComponent,代理模式的 Proxy對應裝飾器模式的Decorator。確實,從代碼實現上看,代
理模式的確與裝飾器模式是一樣的(其實裝飾器模式就是代理模式的一個特殊應用),但是這兩種設計
模式所面向的功能擴展面是不一樣的:
裝飾器模式強調自身功能的擴展。Decorator所做的就是增強ConcreteComponent的功能(也有
可能減弱功能),主體對象為ConcreteComponent,着重類功能的變化;
代理模式強調對代理過程的控制。Proxy 完全掌握對 RealSubject的訪問控制,因此,Proxy 可以決定對RealSubject 進行功能擴展,功能縮減甚至功能散失(不調用RealSubject方法),主體對象為
Proxy;
簡單來講,假設現在小明想租房,那么勢必會有一些事務發生:房源搜索,聯系房東談價格····
假設我們按照代理模式進行思考,那么小明只需找到一個房產中介,讓他去干房源搜索,聯系房東
談價格這些事情,小明只需等待通知然后付點中介費就行了;
而如果采用裝飾器模式進行思考,因為裝飾器模式強調的是自身功能擴展,也就是說,如果要找房
子,小明自身就要增加房源搜索能力擴展,聯系房東談價格能力擴展,通過相應的裝飾器,提升自身能
力,一個人做滿所有的事情。
9.5.裝飾器模式的優缺點
優點:
1、裝飾器是繼承的有力補充,比繼承靈活,不改變原有對象的情況下動態地給一個對象擴展功能,即插即用。
2、通過使用不同裝飾類以及這些裝飾類的排列組合,可以實現不同效果。
3、裝飾器完全遵守開閉原則。
缺點:
1、會出現更多的代碼,更多的類,增加程序復雜性。
2、動態裝飾時,多層裝飾時會更復雜。
那么裝飾器模式我們就講解到這里,希望小伙伴們認真體會,加深理解。
9.6.作業
1、請舉例3-5個使用門面模式的應用場景。
1.解決易用性問題
門面模式可以用來封裝系統的底層實現,隱藏系統的復雜性,提供一組更加簡單易用、更高層的接口。
比如,Linux 系統調用函數就可以看作一種“門面”。它是 Linux 操作系統暴露給開發者的一組“特殊”的編程接口,它封裝了底層更基礎的 Linux 內核調用。
再比如,Linux 的 Shell 命令,實際上也可以看作一種門面模式的應用。它繼續封裝系統調用,提供更加友好、簡單的命令,讓我們可以直接通過執行命令來跟操作系統交互。
2.解決性能問題
我們通過將多個接口調用替換為一個門面接口調用,減少網絡通信成本,提高 App 客戶端的響應速度。
3.解決分布式事務問題
在用戶注冊的時候,我們不僅會創建用戶(在數據庫 User 表中),還會給用戶創建一個錢包(在數據庫的 Wallet 表中)。用戶注冊需要支持事務,也就是說,創建用戶和錢包的兩個操作,要么都成功,要么都失敗,不能一個成功、一個失敗。
最簡單的解決方案是,利用數據庫事務或者 Spring 框架提供的事務(如果是 Java 語言的話),在一個事務中,執行創建用戶和創建錢包這兩個 SQL 操作。這就要求兩個 SQL 操作要在一個接口中完成,所以,我們可以借鑒門面模式的思想,再設計一個包裹這兩個操作的新接口,讓新接口在一個事務中執行兩個 SQL 操作。
2、使用裝飾器模式實現一個可根據權限動態擴展功能的導航條。
例如:GPer社區未登錄狀態下的導航條
GPer社區登錄狀態下的導航條
整體思路:用戶分為未登錄會員、VIP會員、管理員分別展示不同的導航條。
1.創建抽象導航條類,抽象方法展示導航條。
public abstract class AbstractNavDecorator{
/**
* 展示導航條
* @return 導航條
*/
protected abstract String showNavs();
}
2.創建基礎導航條類
public class BaseNavDecorator extends AbstractNavDecorator {
@Override
public String showNavs() {
return "問答-文章-精品課-冒泡-商城";
}
}
3.創建導航條裝飾器
public class NavDecorator extends AbstractNav {
private AbstractNav abstractNav;
public NavDecorator(AbstractNav abstractNav) {
this.abstractNav = abstractNav;
}
@Override
protected String showNavs() {
return this.abstractNav.showNavs();
}
}
4.創建作業裝飾器
public class HomeworkNavDecorator extends NavDecorator {
public HomeworkNavDecorator(AbstractNav abstractNav) {
super(abstractNav);
}
@Override
public String showNavs() {
return super.showNavs() + "-作業";
}
}
5.創建題庫裝飾器
public class QuizNavDecorator extends NavDecorator {
public QuizNavDecorator(AbstractNav abstractNav) {
super(abstractNav);
}
@Override
public String showNavs() {
return super.showNavs() + "-題庫";
}
}
6.創建成長牆裝飾器
public class WallNavDecorator extends NavDecorator {
public WallNavDecorator(AbstractNav abstractNav) {
super(abstractNav);
}
@Override
public String showNavs() {
return super.showNavs() + "-成長牆";
}
}
7.創建管理裝飾器
public class AdminNavDecorator extends NavDecorator {
public AdminNavDecorator(AbstractNav abstractNav) {
super(abstractNav);
}
@Override
protected String showNavs() {
return super.showNavs() + "-管理";
}
}
8.創建用戶枚舉
public enum User {
NOT_LOGIN(1, "未登錄用戶"),
VIP(2, "會員"),
ADMIN(3, "管理員")
;
User(int code, String name) {
this.code = code;
this.type = name;
}
/**
* 編碼
*/
private int code;
/**
* 用戶類型
*/
private String type;
}
9.創建根據用戶創建不同導航條簡單工廠與測試類
public class PermissionFactory {
String showPermission(User user) {
AbstractNav baseNavDecorator = new BaseNav();
switch (user) {
case NOT_LOGIN:
return baseNavDecorator.showNavs();
case VIP:
return new WallNavDecorator(new QuizNavDecorator(new HomeworkNavDecorator(baseNavDecorator))).showNavs();
case ADMIN:
return new AdminNavDecorator(new WallNavDecorator(new QuizNavDecorator(new HomeworkNavDecorator(baseNavDecorator)))).showNavs();
default:
return null;
}
}
public static void main(String[] args){
PermissionFactory permissionFactory = new PermissionFactory();
System.out.println("未登錄用戶導航條展示:");
System.out.println(permissionFactory.showPermission(NOT_LOGIN));
System.out.println("VIP用戶導航條展示:");
System.out.println(permissionFactory.showPermission(VIP));
System.out.println("管理員導航條展示:");
System.out.println(permissionFactory.showPermission(ADMIN));
}
}
10.運行效果如下:
未登錄用戶導航條展示:
問答-文章-精品課-冒泡-商城
VIP用戶導航條展示:
問答-文章-精品課-冒泡-商城-作業-題庫-成長牆
管理員導航條展示:
問答-文章-精品課-冒泡-商城-作業-題庫-成長牆-管理