8.門(mén)面模式
8.1.課程目標(biāo)
1跟继、掌握門(mén)面模式和裝飾器模式的特征和應(yīng)用場(chǎng)景
2种冬、理解裝飾器模式和代理模式的根本區(qū)別。
3舔糖、了解門(mén)面模式的優(yōu)娱两、缺點(diǎn)。
4金吗、了解裝飾器模式的優(yōu)十兢、缺點(diǎn)。
8.2.內(nèi)容定位
1摇庙、定位高級(jí)課程旱物,不太適合接觸業(yè)務(wù)場(chǎng)景比較單一的人群。
2卫袒、深刻了解門(mén)面模式和裝飾器模式的應(yīng)用場(chǎng)景宵呛。
8.3.門(mén)面模式定義
門(mén)面模式(Facade Pattern)又叫外觀模式,提供了一個(gè)統(tǒng)一的接口夕凝,用來(lái)訪問(wèn)子系統(tǒng)中的一群接
口烤蜕。其主要特征是定義了一個(gè)高層接口,讓子系統(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.
解釋?zhuān)阂笠粋€(gè)子系統(tǒng)的外部與其內(nèi)部的通信必須通過(guò)一個(gè)同一的對(duì)象進(jìn)行虎忌。門(mén)面模式提供一個(gè)高層次的接口泡徙,使得 子系統(tǒng)更易于使用。
其實(shí)膜蠢,在我們?nèi)粘5木幋a工作中堪藐,我們都在有意無(wú)意地大量使用門(mén)面模式莉兰,但凡只要高層模塊需要
調(diào)度多個(gè)子系統(tǒng)(2個(gè)以上類(lèi)對(duì)象),我們都會(huì)自覺(jué)地創(chuàng)建一個(gè)新類(lèi)封裝這些子系統(tǒng)礁竞,提供精簡(jiǎn)接口糖荒,
讓高層模塊可以更加容易間接調(diào)用這些子系統(tǒng)的功能。尤其是現(xiàn)階段各種第三方SDK模捂,各種開(kāi)源類(lèi)庫(kù)捶朵,
很大概率都會(huì)使用門(mén)面模式。尤其是你覺(jué)得調(diào)用越方便的狂男,門(mén)面模式使用的一般更多综看。
8.4.門(mén)面模式的應(yīng)用場(chǎng)景
1、子系統(tǒng)越來(lái)越復(fù)雜岖食,增加門(mén)面模式提供簡(jiǎn)單接口
2红碑、構(gòu)建多層系統(tǒng)結(jié)構(gòu),利用門(mén)面對(duì)象作為每層的入口泡垃,簡(jiǎn)化層間調(diào)用析珊。
8.5.門(mén)面模式的通用寫(xiě)法
首先來(lái)看門(mén)面模式的UML類(lèi)圖:
<img src="https://miion.me/image-hosting/image-20200306150145819.png" alt="image-20200306150145819" style="zoom:50%;" />
門(mén)面模式主要包含2種角色:
外觀角色(Facade):也稱(chēng)門(mén)面角色,系統(tǒng)對(duì)外的統(tǒng)一接口蔑穴;
子系統(tǒng)角色(SubSystem):可以同時(shí)有一個(gè)或多個(gè) SubSystem忠寻。每個(gè) SubSytem 都不是一個(gè)單獨(dú)
的類(lèi),而是一個(gè)類(lèi)的集合澎剥。 SubSystem 并不知道 Facade 的存在锡溯,對(duì)于 SubSystem 而言, Facade 只
是另一個(gè)客戶端而已(即 Facade 對(duì) SubSystem 透明)哑姚。
下面是門(mén)面模式的通用代碼祭饭,首先分別創(chuàng)建3個(gè)子系統(tǒng)的業(yè)務(wù)邏輯SubSystemA、SubSystemB叙量、
SubSystemC倡蝙,代碼很簡(jiǎn)單:
// 子系統(tǒng)
public class SubSystemA {
public void doA() {
System.out.println("doing A stuff");
}
}
// 子系統(tǒng)
public class SubSystemB {
public void doB() {
System.out.println("doing B stuff");
}
}
// 子系統(tǒng)
public class SubSystemC {
public void doC() {
System.out.println("doing C stuff");
}
}
來(lái)看客戶端代碼:
// 外觀角色 Facade
public class Facade {
private SubSystemA a = new SubSystemA();
private SubSystemB b = new SubSystemB();
private SubSystemC c = new SubSystemC();
// 對(duì)外接口
public void doA() {
this.a.doA();
}
// 對(duì)外接口
public void doB() {
this.b.doB();
}
// 對(duì)外接口
public void doC() {
this.c.doC();
}
}
8.6.門(mén)面模式業(yè)務(wù)場(chǎng)景實(shí)例
Gper社區(qū)上線了一個(gè)積分兌換禮品的商城,這禮品商城中的大部分功能并不是全部重新開(kāi)發(fā)的绞佩,而是要去對(duì)接已有的各個(gè)子系統(tǒng)(如下圖所示):
<img src="https://miion.me/image-hosting/image-20200306163027892.png" alt="image-20200306163027892" style="zoom:50%;" />
這些子系統(tǒng)可能涉及到積分系統(tǒng)寺鸥、支付系統(tǒng)、物流系統(tǒng)的接口調(diào)用品山。如果所有的接口調(diào)用全部由前
端發(fā)送網(wǎng)絡(luò)請(qǐng)求去調(diào)用現(xiàn)有接口的話胆建,一則會(huì)增加前端開(kāi)發(fā)人員的難度,二則會(huì)增加一些網(wǎng)絡(luò)請(qǐng)求影響
頁(yè)面性能肘交。這個(gè)時(shí)候就可以發(fā)揮門(mén)面模式的優(yōu)勢(shì)了笆载。將所有現(xiàn)成的接口全部整合到一個(gè)類(lèi)中,由后端提
供統(tǒng)一的接口給前端調(diào)用,這樣前端開(kāi)發(fā)人員就不需要關(guān)心各接口的業(yè)務(wù)關(guān)系凉驻,只需要把精力集中在頁(yè)
面交互上腻要。下面我們用代碼來(lái)模擬一下這個(gè)場(chǎng)景。
首先涝登,創(chuàng)建禮品的實(shí)體類(lèi)GiftInfo:
public class GiftInfo {
private String name;
public GiftInfo(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
然后雄家,編寫(xiě)各個(gè)子系統(tǒng)的業(yè)務(wù)邏輯代碼,分別創(chuàng)建積分系統(tǒng)QualifyService類(lèi):
public class QualifyService {
public boolean isAvailable(GiftInfo giftInfo){
System.out.println("校驗(yàn)" +giftInfo.getName() + "積分通過(guò),庫(kù)存通過(guò)胀滚。");
return true;
}
}
支付系統(tǒng)PaymentService類(lèi):
public class PaymentService {
public boolean pay(GiftInfo giftInfo) {
System.out.println("扣減" + giftInfo.getName() + " 積分成功");
return true;
}
}
物流系統(tǒng)ShippingService類(lèi):
public class ShippingService {
public String delivery(GiftInfo giftInfo){
System.out.println(giftInfo.getName() + "進(jìn)入物流系統(tǒng)");
String shippingNo = "666";
return shippingNo;
}
}
然后創(chuàng)建外觀角色GiftFacdeService 類(lèi)趟济,對(duì)外只開(kāi)放一個(gè)兌換禮物的exchange()方法,在 exchange() 方法內(nèi)部整合3個(gè)子系統(tǒng)的所有功能蛛淋。
public class FacadeService {
private QualifyService qualifyService = new QualifyService();
private PaymentService paymentService = new PaymentService();
private ShippingService shippingService = new ShippingService();
//兌換
public void exchange(GiftInfo giftInfo){
//資格校驗(yàn)通過(guò)
if(qualifyService.isAvailable(giftInfo)){
//如果支付積分成功
if(paymentService.pay(giftInfo)){
String shippingNo = shippingService.delivery(giftInfo);
System.out.println("物流系統(tǒng)下單成功咙好,物流單號(hào)是:" + shippingNo);
}
}
}
}
最后,來(lái)看客戶端代碼:
public class Test {
public static void main(String[] args) {
FacadeService facadeService = new FacadeService();
GiftInfo giftInfo = new GiftInfo("《Spring 5核心原理》");
facadeService.exchange(giftInfo);
}
}
運(yùn)行結(jié)果如下:
校驗(yàn)《Spring 5核心原理》積分通過(guò),庫(kù)存通過(guò)褐荷。
扣減《Spring 5核心原理》 積分成功
《Spring 5核心原理》進(jìn)入物流系統(tǒng)
物流系統(tǒng)下單成功勾效,物流單號(hào)是:666
通過(guò)這樣一個(gè)案例對(duì)比之后,相信大家對(duì)門(mén)面模式的印象非常深刻了叛甫。
8.7.門(mén)面模式在源碼中的應(yīng)用
下面我們來(lái)門(mén)面模式在源碼中的應(yīng)用层宫,先來(lái)看Spring JDBC模塊下的JdbcUtils類(lèi),它封裝了和
JDBC相關(guān)的所有操作其监,它一個(gè)代碼片段:
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);
}
}
}
...
}
其他更多的操作萌腿,看它的結(jié)構(gòu)就非常清楚了:
<img src="https://miion.me/image-hosting/image-20200306175133730.png" alt="image-20200306175133730" style="zoom:50%;" />
再來(lái)看一個(gè)MyBatis中的Configuration類(lèi)。它其中有很多new開(kāi)頭的方法抖苦,來(lái)看一下源代碼:
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);
}
上面的這些方法都是對(duì)JDBC中關(guān)鍵組件操作的封裝毁菱。另外地在Tomcat的源碼中也有體現(xiàn),也非常的
有意思锌历。舉個(gè)例子RequestFacade類(lèi)贮庞,來(lái)看源碼:
@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);
}
}
...
}
我們看名字就知道它用了門(mén)面模式。它封裝了非常多的request的操作究西,也整合了很多servlet-api以
外的一些內(nèi)容窗慎,給用戶使用提供了很大便捷。同樣卤材,Tomcat對(duì)Response和Session當(dāng)也封裝了
ResponseFacade和StandardSessionFacade類(lèi)遮斥,感興趣的小伙伴可以去深入了解一下。
8.8.門(mén)面模式的優(yōu)缺點(diǎn)
優(yōu)點(diǎn):
1扇丛、簡(jiǎn)化了調(diào)用過(guò)程术吗,無(wú)需深入了解子系統(tǒng),以防給子系統(tǒng)帶來(lái)風(fēng)險(xiǎn)帆精。
2藐翎、減少系統(tǒng)依賴(lài)材蹬、松散耦合
3、更好地劃分訪問(wèn)層次吝镣,提高了安全性
4、遵循迪米特法則昆庇,即最少知道原則末贾。
缺點(diǎn):
1、當(dāng)增加子系統(tǒng)和擴(kuò)展子系統(tǒng)行為時(shí)整吆,可能容易帶來(lái)未知風(fēng)險(xiǎn)
2拱撵、不符合開(kāi)閉原則
3、某些情況下可能違背單一職責(zé)原則表蝙。
9.裝飾器模式
9.1.裝飾器模式定義
裝飾器模式(Decorator Pattern),也稱(chēng)為包裝模式(Wrapper Pattern)是指在不改變?cè)袑?duì)象
的基礎(chǔ)之上拴测,將功能附加到對(duì)象上,提供了比繼承更有彈性的替代方案(擴(kuò)展原有對(duì)象的功能)府蛇,屬于
結(jié)構(gòu)型模式集索。
原文:Attach additional responsibilities to an object dynamically keeping the same interface. Decorators provide a flexible alternative to subclassing for extending functionality.
解釋?zhuān)簞?dòng)態(tài)地給一個(gè)對(duì)象添加一些額外的職責(zé)。就增加功能來(lái)說(shuō)汇跨,裝飾器模式相比生成子類(lèi)更為靈活务荆。
裝飾器模式的核心是功能擴(kuò)展。使用裝飾器模式可以透明且動(dòng)態(tài)地?cái)U(kuò)展類(lèi)的功能穷遂。
裝飾器模式主要用于透明且動(dòng)態(tài)地?cái)U(kuò)展類(lèi)的功能函匕。其實(shí)現(xiàn)原理為:讓裝飾器實(shí)現(xiàn)被包裝類(lèi)(Concrete
Component)相同的接口(Component)(使得裝飾器與被擴(kuò)展類(lèi)類(lèi)型一致),并在構(gòu)造函數(shù)中傳入
該接口(Component)對(duì)象蚪黑,然后就可以在接口需要實(shí)現(xiàn)的方法中在被包裝類(lèi)對(duì)象的現(xiàn)有功能上添加
新功能了盅惜。而且由于裝飾器與被包裝類(lèi)屬于同一類(lèi)型(均為Component),且構(gòu)造函數(shù)的參數(shù)為其實(shí)
現(xiàn)接口類(lèi)(Component)忌穿,因此裝飾器模式具備嵌套擴(kuò)展功能抒寂,這樣我們就能使用裝飾器模式一層一
層的對(duì)最底層被包裝類(lèi)進(jìn)行功能擴(kuò)展了。
首先看下裝飾器模式的通用UML類(lèi)圖:
從 UML 類(lèi)圖中伴网,我們可以看到蓬推,裝飾器模式 主要包含四種角色:
<img src="https://miion.me/image-hosting/image-20200306204330480.png" alt="image-20200306204330480" style="zoom:50%;" />
抽象組件(Component):可以是一個(gè)接口或者抽象類(lèi),其充當(dāng)被裝飾類(lèi)的原始對(duì)象澡腾,規(guī)定了被
裝飾對(duì)象的行為沸伏;
具體組件(ConcreteComponent):實(shí)現(xiàn)/繼承Component的一個(gè)具體對(duì)象,也即被裝飾對(duì)象动分;
抽象裝飾器(Decorator):通用的裝飾ConcreteComponent的裝飾器毅糟,其內(nèi)部必然有一個(gè)屬性
指向 Component抽象組件;其實(shí)現(xiàn)一般是一個(gè)抽象類(lèi)澜公,主要是為了讓其子類(lèi)按照其構(gòu)造形式傳入一
個(gè) Component 抽象組件姆另,這是強(qiáng)制的通用行為(當(dāng)然喇肋,如果系統(tǒng)中裝飾邏輯單一,并不需要實(shí)現(xiàn)許
多裝飾器迹辐,那么我們可以直接省略該類(lèi)蝶防,而直接實(shí)現(xiàn)一個(gè)具體裝飾器(ConcreteDecorator)即可);
具體裝飾器(ConcreteDecorator):Decorator 的具體實(shí)現(xiàn)類(lèi)明吩,理論上间学,每個(gè) ConcreteDecorator
都擴(kuò)展了Component對(duì)象的一種功能;
總結(jié):裝飾器模式角色分配符合設(shè)計(jì)模式里氏替換原則印荔,依賴(lài)倒置原則低葫,從而使得其具備很強(qiáng)的擴(kuò)展性,最終滿足開(kāi) 閉原則仍律。
9.2.裝飾器模式的應(yīng)用場(chǎng)景
裝飾器模式在我們生活中應(yīng)用也比較多如給煎餅加雞蛋嘿悬;給蛋糕加上一些水果;給房子裝修等水泉,為對(duì)象擴(kuò)展一些額外的職責(zé)善涨。裝飾器在代碼程序中適用于以下場(chǎng)景:
1、用于擴(kuò)展一個(gè)類(lèi)的功能或給一個(gè)類(lèi)添加附加職責(zé)茶行。
2躯概、動(dòng)態(tài)的給一個(gè)對(duì)象添加功能,這些功能可以再動(dòng)態(tài)的撤銷(xiāo)畔师。
3娶靡、需要為一批的兄弟類(lèi)進(jìn)行改裝或加裝功能。
來(lái)看一個(gè)這樣的場(chǎng)景看锉,上班族白領(lǐng)其實(shí)大多有睡懶覺(jué)的習(xí)慣姿锭,每天早上上班都是踩點(diǎn),于是很多小
伙伴為了多賴(lài)一會(huì)兒床都不吃早餐伯铣。那么呻此,也有些小伙伴可能在上班路上碰到賣(mài)煎餅的路邊攤,都會(huì)順
帶一個(gè)到公司茶水間吃早餐腔寡。賣(mài)煎餅的大姐可以給你的煎餅加雞蛋焚鲜,也可以加香腸(如下圖,PS:我買(mǎi)
煎餅一般都要求不加生菜)放前。
<img src="https://miion.me/image-hosting/image-20200306205431629.png" alt="image-20200306205431629" style="zoom:50%;" />
下面我們用代碼還原一下碼農(nóng)的生活忿磅。首先創(chuàng)建一個(gè)煎餅Battercake類(lèi):
public class Battercake {
protected String getMsg(){ return "煎餅";}
public int getPrice(){ return 5;}
}
創(chuàng)建一個(gè)加雞蛋的煎餅BattercakeWithEgg類(lèi):
public class BattercakeWithEgg extends Battercake {
@Override
protected String getMsg(){ return super.getMsg() + "+1個(gè)雞蛋";}
@Override
//加一個(gè)雞蛋加 1 塊錢(qián)
public int getPrice(){ return super.getPrice() + 1;}
}
再創(chuàng)建一個(gè)既加雞蛋又加香腸的BattercakeWithEggAndSausage類(lèi):
public class BattercakeWithEggAndSauage extends BattercakeWithEgg {
@Override
protected String getMsg(){ return super.getMsg() + "+1根香腸";}
@Override
//加一個(gè)香腸加 2 塊錢(qián)
public int getPrice(){ return super.getPrice() + 2;}
}
編寫(xiě)客戶端測(cè)試代碼:
public class Test {
public static void main(String[] args) {
Battercake battercake = new Battercake();
System.out.println(battercake.getMsg() + ",總價(jià):" + battercake.getPrice());
BattercakeWithEgg battercakeWithEgg = new BattercakeWithEgg();
System.out.println(battercakeWithEgg.getMsg() + ",總價(jià):" + battercakeWithEgg.getPrice());
BattercakeWithEggAndSauage battercakeWithEggAndSauage = new BattercakeWithEggAndSauage();
System.out.println(battercakeWithEggAndSauage.getMsg() + ",總價(jià):" + battercakeWithEggAndSauage.getPrice());
}
}
運(yùn)行結(jié)果:
煎餅,總價(jià):5
煎餅+1個(gè)雞蛋,總價(jià):6
煎餅+1個(gè)雞蛋+1根香腸,總價(jià):8
運(yùn)行結(jié)果沒(méi)有問(wèn)題。
但是凭语,如果用戶需要一個(gè)加2個(gè)雞蛋加1根香腸的煎餅葱她,那么用我們現(xiàn)在的類(lèi)
結(jié)構(gòu)是創(chuàng)建不出來(lái)的,也無(wú)法自動(dòng)計(jì)算出價(jià)格似扔,除非再創(chuàng)建一個(gè)類(lèi)做定制吨些。如果需求再變搓谆,一直加定制
顯然是不科學(xué)的。那么下面我們就用裝飾器模式來(lái)解決上面的問(wèn)題豪墅。
首先創(chuàng)建一個(gè)建煎餅的抽象
Battercake類(lèi):
public abstract class Battercake {
protected abstract String getMsg();
protected abstract int getPrice();
}
創(chuàng)建一個(gè)基本的煎餅(或者叫基礎(chǔ)套餐)BaseBattercake:
public class BaseBattercake extends Battercake{
protected String getMsg(){ return "煎餅";}
public int getPrice(){ return 5;}
}
然后泉手,再創(chuàng)建一個(gè)擴(kuò)展套餐的抽象裝飾器BattercakeDecotator類(lèi):
public class BattercakeDecorator extends Battercake{
//靜態(tài)代理,委派
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();}
}
然后偶器,創(chuàng)建雞蛋裝飾器EggDecorator類(lèi):
public class EggDecorator extends BattercakeDecorator{
public EggDecorator(Battercake battercake) {
super(battercake);
}
@Override
protected String getMsg(){ return super.getMsg() + "1個(gè)雞蛋";}
@Override
public int getPrice(){ return super.getPrice() + 1;}
}
創(chuàng)建香腸裝飾器SausageDecorator類(lèi):
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;}
}
編寫(xiě)客戶端測(cè)試代碼:
public class Test {
public static void main(String[] args) {
//路邊攤買(mǎi)一個(gè)煎餅
Battercake battercake = new BaseBattercake();
//煎餅有點(diǎn)小螃诅,想再加一個(gè)雞蛋
battercake = new EggDecorator(battercake);
//再加一個(gè)雞蛋
battercake = new EggDecorator(battercake);
//很餓,再加根香腸
battercake = new SauageDecorator(battercake);
//跟靜態(tài)代理最大區(qū)別就是職責(zé)不同
//靜態(tài)代理不一定要滿足 is-a 的關(guān)系
//靜態(tài)代理會(huì)做功能增強(qiáng)状囱,同一個(gè)職責(zé)變得不一樣
//裝飾器更多考慮是擴(kuò)展
System.out.println(battercake.getMsg() + ",總價(jià)" + battercake.getPrice());
}
}
運(yùn)行結(jié)果:
煎餅,總價(jià):5
煎餅+1個(gè)雞蛋,總價(jià):6
煎餅+1個(gè)雞蛋+1根香腸,總價(jià):8
來(lái)看一下類(lèi)圖:
<img src="https://miion.me/image-hosting/image-20200306213549376.png" alt="image-20200306213549376" style="zoom:50%;" />
為了加深印象,我們?cè)賮?lái)看一個(gè)應(yīng)用場(chǎng)景倘是。需求大致是這樣亭枷,系統(tǒng)采用的是sls服務(wù)監(jiān)控項(xiàng)目日志,
以Json的格式解析搀崭,所以需要將項(xiàng)目中的日志封裝成json格式再打印∵墩常現(xiàn)有的日志體系采用了log4j+
slf4j框架搭建而成×龆茫客戶端調(diào)用是這樣的:
private static final Logger logger = LoggerFactory.getLogger(Component.class);
logger.error(string);
這樣打印出來(lái)的是毫無(wú)規(guī)則的一行行字符串升敲。在考慮將其轉(zhuǎn)換成json格式時(shí),我采用了裝飾器模式轰传。
目前有的是統(tǒng)一接口 Logger 和其具體實(shí)現(xiàn)類(lèi)驴党,我要加的就是一個(gè)裝飾類(lèi)和真正封裝成 Json格式的裝
飾產(chǎn)品類(lèi)。創(chuàng)建裝飾器類(lèi)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) {
}
//省略其他默認(rèn)實(shí)現(xiàn)
}
創(chuàng)建具體組件JasonLogger類(lèi)實(shí)現(xiàn)代碼如下:
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(){
//拼裝了一些運(yùn)行時(shí)信息
return new JSONObject();
}
}
可以看到获茬,在JsonLogger中港庄,對(duì)于Logger的各種接口,我都用JsonObject對(duì)象進(jìn)行一層封裝恕曲。
在打印的時(shí)候鹏氧,最終還是調(diào)用原生接口 logger.error(string),只是這個(gè) string 參數(shù)已經(jīng)被我們裝飾過(guò)
了佩谣。如果有額外的需求把还,我們也可以再寫(xiě)一個(gè)函數(shù)去實(shí)現(xiàn)。比如 error(Exception e)茸俭,只傳入一個(gè)異常
對(duì)象吊履,這樣在調(diào)用時(shí)就非常方便了。
另外瓣履,為了在新老交替的過(guò)程中盡量不改變太多的代碼和使用方式率翅。我又在JsonLogger 中加入了
一個(gè)內(nèi)部的工廠類(lèi)JsonLoggerFactory(這個(gè)類(lèi)轉(zhuǎn)移到DecoratorLogger 中可能更好一些),他包含
一個(gè)靜態(tài)方法袖迎,用于提供對(duì)應(yīng)的JsonLogger實(shí)例冕臭。最終在新的日志體系中腺晾,使用方式如下:
public class Test {
private static final Logger logger = JsonLoggerFactory.getLogger(Test.class);
public static void main(String[] args) {
logger.error("系統(tǒng)錯(cuò)誤");
}
}
對(duì)于客戶端而言,唯一與原先不同的地方就是將 LoggerFactory 改為JsonLoggerFactory 即可辜贵,
這樣的實(shí)現(xiàn)悯蝉,也會(huì)被更快更方便的被其他開(kāi)發(fā)者接受和習(xí)慣。最后看一下類(lèi)圖:
<img src="https://miion.me/image-hosting/image-20200306215623864.png" alt="image-20200306215623864" style="zoom:50%;" />
裝飾器模式最本質(zhì)的特征是講原有類(lèi)的附加功能抽離出來(lái)托慨,簡(jiǎn)化原有類(lèi)的邏輯鼻由。通過(guò)這樣兩個(gè)案例,我們可以總結(jié)出來(lái)厚棵,其實(shí)抽象的裝飾器是可有可無(wú)的蕉世,具體可以根據(jù)業(yè)務(wù)模型來(lái)選擇。
9.3.裝飾器模式在源碼中的應(yīng)用
裝飾器模式在源碼中也應(yīng)用得非常多婆硬,在 JDK 中體現(xiàn)最明顯的類(lèi)就是 IO 相關(guān)的類(lèi)狠轻,如
BufferedReader、InputStream彬犯、OutputStream向楼,看一下常用的InputStream的類(lèi)結(jié)構(gòu)圖:
<img src="https://miion.me/image-hosting/image-20200306221314640.png" alt="image-20200306221314640" style="zoom: 50%;" />
在Spring 中的 TransactionAwareCacheDecorator類(lèi)我們也可以來(lái)嘗試?yán)斫庖幌拢@個(gè)類(lèi)主要是用來(lái)處理事務(wù)緩存的谐区,來(lái)看一下代碼:
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就是對(duì)Cache的一個(gè)包裝湖蜕。再來(lái)看一個(gè) MVC中的裝飾器模式
HttpHeadResponseDecorator類(lèi):
public class HttpHeadResponseDecorator extends ServerHttpResponseDecorator {
public HttpHeadResponseDecorator(ServerHttpResponse delegate) {
super(delegate);
}
...
}
最后,看看 MyBatis中的一段處理緩存的設(shè)計(jì) org.apache.ibatis.cache.Cache 類(lèi)宋列,找到它的包定
位:
從名字上來(lái)看其實(shí)更容易理解了昭抒。比如 FifoCache先入先出算法的緩存;LruCache最近最少使用
的緩存虚茶;TransactionlCache事務(wù)相關(guān)的緩存戈鲁,都是采用裝飾器模式。MyBatis源碼在我們后續(xù)的課程
也會(huì)深入講解嘹叫,感興趣的小伙伴可以詳細(xì)看看這塊的源碼婆殿,也可以好好學(xué)習(xí)一下MyBatis的命名方式,
今天我們還是把重點(diǎn)放到設(shè)計(jì)模式上罩扇。
9.4.裝飾器模式和代理模式對(duì)比
從代理模式的 UML類(lèi)圖和通用代碼實(shí)現(xiàn)上看婆芦,代理模式與裝飾器模式幾乎一模一樣。代理模式的
Subject 對(duì) 應(yīng) 裝 飾 器 模 式 的 Component 喂饥, 代 理 模 式 的 RealSubject 對(duì) 應(yīng) 裝 飾 器 模 式 的
ConcreteComponent消约,代理模式的 Proxy對(duì)應(yīng)裝飾器模式的Decorator。確實(shí)员帮,從代碼實(shí)現(xiàn)上看或粮,代
理模式的確與裝飾器模式是一樣的(其實(shí)裝飾器模式就是代理模式的一個(gè)特殊應(yīng)用),但是這兩種設(shè)計(jì)
模式所面向的功能擴(kuò)展面是不一樣的:
裝飾器模式強(qiáng)調(diào)自身功能的擴(kuò)展捞高。Decorator所做的就是增強(qiáng)ConcreteComponent的功能(也有
可能減弱功能)氯材,主體對(duì)象為ConcreteComponent渣锦,著重類(lèi)功能的變化;
代理模式強(qiáng)調(diào)對(duì)代理過(guò)程的控制氢哮。Proxy 完全掌握對(duì) RealSubject的訪問(wèn)控制袋毙,因此,Proxy 可以決定對(duì)RealSubject 進(jìn)行功能擴(kuò)展冗尤,功能縮減甚至功能散失(不調(diào)用RealSubject方法)听盖,主體對(duì)象為
Proxy;
簡(jiǎn)單來(lái)講裂七,假設(shè)現(xiàn)在小明想租房皆看,那么勢(shì)必會(huì)有一些事務(wù)發(fā)生:房源搜索,聯(lián)系房東談價(jià)格····
假設(shè)我們按照代理模式進(jìn)行思考背零,那么小明只需找到一個(gè)房產(chǎn)中介悬蔽,讓他去干房源搜索,聯(lián)系房東
談價(jià)格這些事情捉兴,小明只需等待通知然后付點(diǎn)中介費(fèi)就行了;
而如果采用裝飾器模式進(jìn)行思考录语,因?yàn)檠b飾器模式強(qiáng)調(diào)的是自身功能擴(kuò)展倍啥,也就是說(shuō),如果要找房
子澎埠,小明自身就要增加房源搜索能力擴(kuò)展虽缕,聯(lián)系房東談價(jià)格能力擴(kuò)展,通過(guò)相應(yīng)的裝飾器蒲稳,提升自身能
力氮趋,一個(gè)人做滿所有的事情。
9.5.裝飾器模式的優(yōu)缺點(diǎn)
優(yōu)點(diǎn):
1江耀、裝飾器是繼承的有力補(bǔ)充剩胁,比繼承靈活,不改變?cè)袑?duì)象的情況下動(dòng)態(tài)地給一個(gè)對(duì)象擴(kuò)展功能祥国,即插即用昵观。
2、通過(guò)使用不同裝飾類(lèi)以及這些裝飾類(lèi)的排列組合舌稀,可以實(shí)現(xiàn)不同效果啊犬。
3、裝飾器完全遵守開(kāi)閉原則壁查。
缺點(diǎn):
1觉至、會(huì)出現(xiàn)更多的代碼,更多的類(lèi)睡腿,增加程序復(fù)雜性语御。
2峻贮、動(dòng)態(tài)裝飾時(shí),多層裝飾時(shí)會(huì)更復(fù)雜沃暗。
那么裝飾器模式我們就講解到這里月洛,希望小伙伴們認(rèn)真體會(huì),加深理解孽锥。
9.6.作業(yè)
1嚼黔、請(qǐng)舉例3-5個(gè)使用門(mén)面模式的應(yīng)用場(chǎng)景。
1.解決易用性問(wèn)題
門(mén)面模式可以用來(lái)封裝系統(tǒng)的底層實(shí)現(xiàn)惜辑,隱藏系統(tǒng)的復(fù)雜性唬涧,提供一組更加簡(jiǎn)單易用、更高層的接口盛撑。
比如碎节,Linux 系統(tǒng)調(diào)用函數(shù)就可以看作一種“門(mén)面”。它是 Linux 操作系統(tǒng)暴露給開(kāi)發(fā)者的一組“特殊”的編程接口抵卫,它封裝了底層更基礎(chǔ)的 Linux 內(nèi)核調(diào)用狮荔。
再比如,Linux 的 Shell 命令介粘,實(shí)際上也可以看作一種門(mén)面模式的應(yīng)用殖氏。它繼續(xù)封裝系統(tǒng)調(diào)用,提供更加友好姻采、簡(jiǎn)單的命令雅采,讓我們可以直接通過(guò)執(zhí)行命令來(lái)跟操作系統(tǒng)交互。
2.解決性能問(wèn)題
我們通過(guò)將多個(gè)接口調(diào)用替換為一個(gè)門(mén)面接口調(diào)用慨亲,減少網(wǎng)絡(luò)通信成本婚瓜,提高 App 客戶端的響應(yīng)速度。
3.解決分布式事務(wù)問(wèn)題
在用戶注冊(cè)的時(shí)候刑棵,我們不僅會(huì)創(chuàng)建用戶(在數(shù)據(jù)庫(kù) User 表中)巴刻,還會(huì)給用戶創(chuàng)建一個(gè)錢(qián)包(在數(shù)據(jù)庫(kù)的 Wallet 表中)。用戶注冊(cè)需要支持事務(wù)蛉签,也就是說(shuō)冈涧,創(chuàng)建用戶和錢(qián)包的兩個(gè)操作,要么都成功正蛙,要么都失敗督弓,不能一個(gè)成功、一個(gè)失敗乒验。
最簡(jiǎn)單的解決方案是愚隧,利用數(shù)據(jù)庫(kù)事務(wù)或者 Spring 框架提供的事務(wù)(如果是 Java 語(yǔ)言的話),在一個(gè)事務(wù)中,執(zhí)行創(chuàng)建用戶和創(chuàng)建錢(qián)包這兩個(gè) SQL 操作狂塘。這就要求兩個(gè) SQL 操作要在一個(gè)接口中完成录煤,所以,我們可以借鑒門(mén)面模式的思想荞胡,再設(shè)計(jì)一個(gè)包裹這兩個(gè)操作的新接口妈踊,讓新接口在一個(gè)事務(wù)中執(zhí)行兩個(gè) SQL 操作。
2泪漂、使用裝飾器模式實(shí)現(xiàn)一個(gè)可根據(jù)權(quán)限動(dòng)態(tài)擴(kuò)展功能的導(dǎo)航條廊营。
例如:GPer社區(qū)未登錄狀態(tài)下的導(dǎo)航條
GPer社區(qū)登錄狀態(tài)下的導(dǎo)航條
整體思路:用戶分為未登錄會(huì)員、VIP會(huì)員萝勤、管理員分別展示不同的導(dǎo)航條露筒。
1.創(chuàng)建抽象導(dǎo)航條類(lèi),抽象方法展示導(dǎo)航條敌卓。
public abstract class AbstractNavDecorator{
/**
* 展示導(dǎo)航條
* @return 導(dǎo)航條
*/
protected abstract String showNavs();
}
2.創(chuàng)建基礎(chǔ)導(dǎo)航條類(lèi)
public class BaseNavDecorator extends AbstractNavDecorator {
@Override
public String showNavs() {
return "問(wèn)答-文章-精品課-冒泡-商城";
}
}
3.創(chuàng)建導(dǎo)航條裝飾器
public class NavDecorator extends AbstractNav {
private AbstractNav abstractNav;
public NavDecorator(AbstractNav abstractNav) {
this.abstractNav = abstractNav;
}
@Override
protected String showNavs() {
return this.abstractNav.showNavs();
}
}
4.創(chuàng)建作業(yè)裝飾器
public class HomeworkNavDecorator extends NavDecorator {
public HomeworkNavDecorator(AbstractNav abstractNav) {
super(abstractNav);
}
@Override
public String showNavs() {
return super.showNavs() + "-作業(yè)";
}
}
5.創(chuàng)建題庫(kù)裝飾器
public class QuizNavDecorator extends NavDecorator {
public QuizNavDecorator(AbstractNav abstractNav) {
super(abstractNav);
}
@Override
public String showNavs() {
return super.showNavs() + "-題庫(kù)";
}
}
6.創(chuàng)建成長(zhǎng)墻裝飾器
public class WallNavDecorator extends NavDecorator {
public WallNavDecorator(AbstractNav abstractNav) {
super(abstractNav);
}
@Override
public String showNavs() {
return super.showNavs() + "-成長(zhǎng)墻";
}
}
7.創(chuàng)建管理裝飾器
public class AdminNavDecorator extends NavDecorator {
public AdminNavDecorator(AbstractNav abstractNav) {
super(abstractNav);
}
@Override
protected String showNavs() {
return super.showNavs() + "-管理";
}
}
8.創(chuàng)建用戶枚舉
public enum User {
NOT_LOGIN(1, "未登錄用戶"),
VIP(2, "會(huì)員"),
ADMIN(3, "管理員")
;
User(int code, String name) {
this.code = code;
this.type = name;
}
/**
* 編碼
*/
private int code;
/**
* 用戶類(lèi)型
*/
private String type;
}
9.創(chuàng)建根據(jù)用戶創(chuàng)建不同導(dǎo)航條簡(jiǎn)單工廠與測(cè)試類(lèi)
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("未登錄用戶導(dǎo)航條展示:");
System.out.println(permissionFactory.showPermission(NOT_LOGIN));
System.out.println("VIP用戶導(dǎo)航條展示:");
System.out.println(permissionFactory.showPermission(VIP));
System.out.println("管理員導(dǎo)航條展示:");
System.out.println(permissionFactory.showPermission(ADMIN));
}
}
10.運(yùn)行效果如下:
未登錄用戶導(dǎo)航條展示:
問(wèn)答-文章-精品課-冒泡-商城
VIP用戶導(dǎo)航條展示:
問(wèn)答-文章-精品課-冒泡-商城-作業(yè)-題庫(kù)-成長(zhǎng)墻
管理員導(dǎo)航條展示:
問(wèn)答-文章-精品課-冒泡-商城-作業(yè)-題庫(kù)-成長(zhǎng)墻-管理