從原始碼角度理解Java設計模式——裝飾者模式
一、飾器者模式介紹
裝飾者模式定義:在不改變原有物件的基礎上附加功能,相比生成子類更靈活。
適用場景:動態的給一個物件新增或者撤銷功能。
優點:可以不改變原有物件的情況下動態擴充套件功能,可以使擴充套件的多個功能按想要的順序執行,以實現不同效果。
缺點:更多的類,使程式複雜
型別:結構型。
類圖:
原始碼分析中的典型應用
- Java I/O 中的裝飾者模式
- spring session 中的裝飾者模式
- Mybatis 快取中的裝飾者模式
二、給系統新增日誌,安全、限流示例
一般系統的安全、日誌、限流等業務無關程式碼可以抽離出來,在Controller前後用切面改造,模板方法模式可以部分解決這個問題:
public abstract class BaseAspect { Logger logger = LoggerFactory.getLogger(BaseCommand.class); public void execute(){ //記錄日誌 logger.debug("..start.."); //過濾跨站指令碼攻擊 paramXssAspect(); //限制速率 doRateLimit(); doBusiness(); logger.debug("..end.."); } public abstract void doBusiness(); } class PlaceOrderAspect extends BaseAspect { @Override public void doBusiness() { //下單操作 } } class PayOrderAspect extends BaseAspect { @Override public void doBusiness() { //支付操作 } }
在父類中已經把”亂七八糟“的非業務程式碼寫好了,只留了一個抽象方法等子類去實現,子類變的很清爽,只需關注業務邏輯就可以了。
這種方式最大的缺陷就是父類會定義一切:要執行那些非業務程式碼,以什麼順序執行等等,子類只能無條件接受。如果有一個子類,不限制速率,那麼它也沒有辦法把它去掉。
利用裝飾者模式,針對上面的問題,可以變的很靈活。
//最高層抽象元件 interface IAspect { String doHandlerAspect(); } //基本被裝飾類,做一些公共處理 class AspectImpl implements IAspect{ @Override public String doHandlerAspect() { return "裸跑程式碼."; } } abstract class AbstractDecorator implements IAspect{ //很重要,組合抽象構件到自己的類中 private IAspect aspect; public AbstractDecorator(IAspect aspect) {//通過IAspect構造自己 this.aspect = aspect; } @Override public String doHandlerAspect() { return this.aspect.doHandlerAspect(); } }
附加記錄日誌,安全,限流功能:
class LoggerAspect extends AbstractDecorator{
public LoggerAspect(IAspect aspect){
super(aspect);
}
@Override
public String doHandlerAspect() {
return super.doHandlerAspect()+"+記錄日誌.";
}
}
class ParamXssAspect extends AbstractDecorator{
public ParamXssAspect(IAspect aspect){
super(aspect);
}
@Override
public String doHandlerAspect() {
return super.doHandlerAspect()+"+過濾危險字元.";
}
}
class LimitAspect extends AbstractDecorator{
public LimitAspect(IAspect aspect){
super(aspect);
}
@Override
public String doHandlerAspect() {
return super.doHandlerAspect()+"+限流.";
}
}
測試一下:
public class Test {
public static void main(String[] args) {
IAspect aspect = new LimitAspect(new ParamXssAspect(new LoggerAspect(new AspectImpl())));
System.out.println(aspect.doHandlerAspect());
}
}
執行結果:
------
裸跑程式碼.+記錄日誌.+過濾危險字元.+限流.
------
通過上面可以看出,裝飾者模式可以任意次序組裝功能,是不是很靈活?另外,也可以把上述三個功能封裝成註解@Log、@ParamXss、@AccessLimit,實現可拔插。如果讀者想看註解功能完整實現,可以參考我的這個專案:SpringBoot+JWT+Shiro+MybatisPlus實現Restful快速開發後端腳手架
三、原始碼中的裝飾者模式
3.1、Java IO中是體現最明顯的裝飾者模式。
它基於字元流(InputStream/OutputStream) 和 位元組流(Reader/Writer)作為基類,下面畫出InputStream、Reader的部分類圖:
這裡總結幾種常用流的應用場景:
流名稱 | 應用場景 |
---|---|
ByteArrayInputStream | 訪問陣列,把記憶體中的一個緩衝區作為 InputStream 使用,CPU從快取區讀取資料比從儲存介質的速率快10倍以上 |
StringBufferInputStream | 把一個 String 物件作為。InputStream。不建議使用,在轉換字元的問題上有缺陷 |
FileInputStream | 訪問檔案,把一個檔案作為 InputStream ,實現對檔案的讀取操作 |
PipedInputStream | 訪問管道,主要線上程中使用,一個執行緒通過管道輸出流傳送資料,而另一個執行緒通過管道輸入流讀取資料,這樣可實現兩個執行緒間的通訊 |
SequenceInputStream | 把多個 InputStream 合併為一個 InputStream . “序列輸入流”類允許應用程式把幾個輸入流連續地合併起來 |
DataInputStream | 特殊流,讀各種基本型別資料,如byte、int、String的功能 |
ObjectInputStream | 物件流,讀物件的功能 |
PushBackInputStream | 推回輸入流,可以把讀取進來的某些資料重新回退到輸入流的緩衝區之中 |
BufferedInputStream | 緩衝流,增加了緩衝功能 |
3.2、Spring Session中的ServletRequestWrapper(Response也一樣)的裝飾者模式。
public class ServletRequestWrapper implements ServletRequest {
private ServletRequest request;//組合抽象介面到自己的類中
public ServletRequestWrapper(ServletRequest request) {//可以構造自己
if(request == null) {
throw new IllegalArgumentException("Request cannot be null");
} else {
this.request = request;
}
}
public ServletRequest getRequest() {
return this.request;
}
public void setRequest(ServletRequest request) {
if(request == null) {
throw new IllegalArgumentException("Request cannot be null");
} else {
this.request = request;
}
}
//省略...
}
3.3、Spring Cache中的TransactionAwareCacheDecorator的裝飾者模式。
其實從類名就可以看出。
public class TransactionAwareCacheDecorator implements Cache {
private final Cache targetCache;//把Cache組合到自己類中
public TransactionAwareCacheDecorator(Cache targetCache) {//通過Cache構造自己
Assert.notNull(targetCache, "Target Cache must not be null");
this.targetCache = targetCache;
}
public <T> T get(Object key, Class<T> type) {
return this.targetCache.get(key, type);
}
public void put(final Object key, final Object value) {
// 判斷是否開啟了事務
if (TransactionSynchronizationManager.isSynchronizationActive()) {
// 將操作註冊到 afterCommit 階段
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
public void afterCommit() {
TransactionAwareCacheDecorator.this.targetCache.put(key, value);
}
});
} else {
this.targetCache.put(key, value);
}
}
// ...省略...
}
3.4、Mybatis中的裝飾者。
Cache為抽象構件類,PerpetualCache為具體構件類,decorators包下的類為裝飾類,這裡沒有抽象裝飾類。
參考:
《碼農翻身》劉欣