1. 程式人生 > >從原始碼角度理解Java設計模式——裝飾者模式

從原始碼角度理解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包下的類為裝飾類,這裡沒有抽象裝飾類。

Mybatis cache 中的装饰者模式

參考:

設計模式 | 裝飾者模式及典型應用

《碼農翻身》劉欣