原文地址
一、設計模式為啥老是用不好?
想要寫出更屌的程式碼,提高程式碼的健壯性和可擴充套件性,那麼設計模式可謂是必學的技能。
關於學習設計模式,大家可能都覺得設計模式的概念太過於抽象,理解起來有點費勁;又或者看的時候是理解了,但是寫起程式碼時,卻毫無頭緒,壓根不知道可以套用哪個設計模式。
對,可以看到我使用了 “套” 這個字眼,正是因為我們無法深入理解設計模式的設計理念和使用場景,所以我們往往是想讓我們的程式碼套用設計模式,而不理會業務場景是否合適。
關於設計模式的學習,我不會推薦任何書,因為我自己也沒看過,哈哈哈。我看過的是龍哥的設計模式系列文章,裡面的文章不但會介紹設計模式的概念,也會用非常有趣的場景去講解設計模式的設計理念,下面先分享一波連結:龍哥設計模式全集。
對於我自己而言,關於設計模式的使用,除非是非常深刻的理解了,又或者某種設計模式的使用場景非常的清晰明確(例如建立型設計模式中的單例模式、結構型設計模式中的組合模式、行為型設計模式中的策略模式等等),不然我也不知道該如何使用,和什麼時候使用。
二、在閱讀開源框架原始碼中學習設計模式!
想學習設計模式的使用方式,何不研究一下各大優秀的開源框架的原始碼。
想更深層次的理解設計模式,往往閱讀優秀的框架和中介軟體的原始碼是非常好的方式。優秀的開源框架和中介軟體,裡面都使用了大量的設計模式,使得框架的實用性、可擴充套件性和效能非常的高。
很巧,今天在工作的空餘時間中,我繼續閱讀一本關於併發的書,並看到關於 Netty 的內建解碼器,其中最常用的有 ReplayingDecoder,它是 ByteToMessageDecoder 的子類,作用是: 在讀取ByteBuf緩衝區的資料之前,需要檢查緩衝區是否有足夠的位元組;若ByteBuf中有足夠的位元組,則會正常讀取;反之,如果沒有足夠的位元組,則會停止解碼。
它是如何做到自主控制解碼的時機的呢?其實底層是使用了 ReplayingDecoderByteBuf 這個繼承於 ByteBuf 的實現類。而它使用了裝飾器設計模式。
1、在 Netty 中如何自定義實現整數解碼器?
1.1、ByteToMessageDecoder:
我們需要自定義類需要繼承 ByteToMessageDecoder 抽象類,然後重寫 decode 方法即可。
看程式碼:
/**
* @author Howinfun
* @desc
* @date 2020/8/21
*/
public class MyIntegerDecoder extends ByteToMessageDecoder {
@Override
protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List<Object> list) throws Exception {
while (byteBuf.readableBytes() >= 4){
int num = byteBuf.readInt();
System.out.println("解碼出一個整數:"+num);
list.add(num);
}
}
}
我們可以看到非常的簡單,就是不斷地判斷緩衝區裡的的可讀位元組數是否大於等於4(Java 中整數的大小);如果是的話就讀取4個位元組大小的內容,然後放到結果集裡面。
1.2、ReplayingDecoder:
我們需要自定義類需要繼承 ReplayingDecoder 類,然後重寫 decode 方法即可。
看程式碼:
/**
* @author Howinfun
* @desc
* @date 2020/8/21
*/
public class MyIntegerDecoder2 extends ReplayingDecoder {
@Override
protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List<Object> list) throws Exception {
int num = byteBuf.readInt();
System.out.println("解碼出一個整數:"+num);
list.add(num);
}
}
這個實現更加簡單,那就是去掉判斷,直接呼叫 ByteBuf 的 readInt() 方法去獲取整數即可。
1.3、測試用例:
1.3.1、自定義業務處理器:
先建立一個業務處理器 IntegerProcessHandler,用於處理上面的自定義解碼器解碼之後的 Java Integer 整數。其功能是:讀取上一站的入站資料,把它轉換成整數,並且輸出到Console控制檯上。
碼如下:
/**
* @author Howinfun
* @desc
* @date 2020/8/21
*/
public class IntegerProcessorHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
Integer integer = (Integer) msg;
System.out.println("列印出一個整數:"+integer);
}
}
這個業務處理器非常的簡單,直接繼承 ChannelInBoundHandlerAdapter,然戶重寫 channelRead() 方法即可。
1.3.2、利用 EmbeddedChannel 進行測試:
為了測試入站處理器,需要確保通道能接收到 ByteBuf 入站資料。這裡呼叫 writeInbound 方法,模擬入站資料的寫入,向嵌入式通道 EmbeddedChannel 寫入100次 ByteBuf 入站緩衝;每一次寫入僅僅包含一個整數。
EmbeddedChannel 的 writeInbound 方法模擬入站資料,會被流水線上的兩個入站處理器所接收和處理。接著,這些入站的二進位制位元組被解碼成一個一個的整數,然後逐個地輸出到控制檯上。
看程式碼:
public class Test{
public static void main(String[] args){
ChannelInitializer i = new ChannelInitializer<EmbeddedChannel>() {
@Override
protected void initChannel(EmbeddedChannel channel) throws Exception {
// 繼承 ByteToMessageDecoder 抽象類的自定義解碼器
// channel.pipeline().addLast(new MyIntegerDecoder()).addLast(new IntegerProcessorHandler());
// 繼承 ReplayingDecoder 類的自定義解碼器
channel.pipeline().addLast(new MyIntegerDecoder2()).addLast(new IntegerProcessorHandler());
}
};
EmbeddedChannel channel = new EmbeddedChannel(i);
for (int j = 0;j < 20;j++){
ByteBuf byteBuf = Unpooled.buffer();
byteBuf.writeInt(j);
channel.writeInbound(byteBuf);
}
ThreadUtil.sleep(Integer.MAX_VALUE);
}
}
通過測試,兩個自定義 Decoder 都是沒問題的。而他們的最大不同點在於:繼承抽象類 ByteToMessageDecoder 的解碼器需要判斷可讀位元組數是否大於等於4,大於等於才可以讀取一個整數出來;而繼承 ReplayingDecoder 的解碼器直接呼叫 readInt() 方法即可。
2、解讀 ReplayingDecoder 的原理
其實其中的原理非常的簡單,我們可以直接從 ReplayingDecoder 的原始碼入手:
2.1、ReplayingDecoder的建構函式:
首先是建構函式,此處我們用了無參建構函式:
protected ReplayingDecoder() {
this((Object)null);
}
protected ReplayingDecoder(S initialState) {
this.replayable = new ReplayingDecoderByteBuf();
this.checkpoint = -1;
this.state = initialState;
}
我們可以看到,主要是初始化了 ReplayingDecoderByteBuf(其實就是加了點料的 ByteBuf)、checkpoint(讀指標下標) 和 state。我們這篇文章不需要理會 state 屬性,這個屬性是稍微高階一點的用法。
我們最需要關注的是 ReplayingDecoderByteBuf 這個類。
2.2、繼續探討 ReplayingDecoderByteBuf:
那麼接下來看看 ReplayingDecoderByteBuf 的原始碼。
2.2.1、ReplayingDecoderByteBuf 的屬性:
final class ReplayingDecoderByteBuf extends ByteBuf {
private static final Signal REPLAY;
private ByteBuf buffer;
private boolean terminated;
private SwappedByteBuf swapped;
static final ReplayingDecoderByteBuf EMPTY_BUFFER;
ReplayingDecoderByteBuf() {
}
//...
}
我們可以看到,它繼承了 ByteBuf 抽象類,並且裡面包含一個 ByteBuf 型別的 buffer 屬性,剩餘的其他屬性暫時不需要看懂。
2.2.2、瞧一瞧 readInt() 方法:
那麼接下來,我們就是直接看 ReplayingDecoderByteBuf 的 readInt() 方法了,因為我們知道,在上面的自定義解碼器 MyIntegerDecoder2 的 decode() 方法中,只需要直接呼叫 ByteBuf(也就是 ReplayingDecoderByteBuf) 的 readInt() 方法即可解碼一個整數。
public int readInt() {
this.checkReadableBytes(4);
return this.buffer.readInt();
}
readInt() 方法非常簡單,首先是呼叫 checkReadableBytes() 方法,並且傳入 4。根據方法名,我們就可以猜到,先判斷緩衝區中是否有4個可讀位元組;如果是的話,就呼叫 buffer 的 readInt() 方法,讀取一個整數。
2.2.3、繼續看看 checkReadableBytes() 方法:
程式碼如下:
private void checkReadableBytes(int readableBytes) {
if (this.buffer.readableBytes() < readableBytes) {
throw REPLAY;
}
}
方法非常簡單,其實和我們上面的 MyIntegerDecoder 一樣,就是判斷緩衝區中是否有 4個位元組的可讀資料,如果不是的話,則丟擲異常。
2.2.4、Signal 異常:
而我們最需要關注的就是這個異常,這個異常是 ReplayingDecoder 的靜態成員變數。它是繼承了 error 的異常類,是 netty 提供配合 ReplayingDecoder 一起使用的。
至於如何使用,我們可以看到 ReplayingDecoder 的 callDecode() 方法:
protected void callDecode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {
// 呼叫 ReplayingDecoderByteBuf 的 setCumulation() 方法,使用 ReplayingDecoderByteBuf 裝飾 ByteBuf
this.replayable.setCumulation(in);
try {
while(in.isReadable()) {
int oldReaderIndex = this.checkpoint = in.readerIndex();
int outSize = out.size();
if (outSize > 0) {
// 將結果集流到下一個 InBoundChannel
fireChannelRead(ctx, out, outSize);
out.clear();
if (ctx.isRemoved()) {
break;
}
outSize = 0;
}
S oldState = this.state;
int oldInputLength = in.readableBytes();
try {
// 呼叫自定義解碼器的 decode() 方法進行解碼
this.decodeRemovalReentryProtection(ctx, this.replayable, out);
if (ctx.isRemoved()) {
break;
}
if (outSize == out.size()) {
if (oldInputLength == in.readableBytes() && oldState == this.state) {
throw new DecoderException(StringUtil.simpleClassName(this.getClass()) + ".decode() must consume the inbound data or change its state if it did not decode anything.");
}
continue;
}
} catch (Signal var10) {
// 如果不是 Sinal 異常,則往外拋
var10.expect(REPLAY);
if (!ctx.isRemoved()) {
// 設定讀指標為原來的位置
int checkpoint = this.checkpoint;
if (checkpoint >= 0) {
in.readerIndex(checkpoint);
}
}
break;
}
// ......
}
} catch (DecoderException var11) {
throw var11;
} catch (Exception var12) {
throw new DecoderException(var12);
}
}
到這裡,我們可以捋一下思路:
- 當緩衝區資料流到繼承 ReplayingDecoder 的解碼器時,會先判斷結果集是否有資料,如果有則流入到下一個 InBoundChannel;
- 接著會呼叫自定義解碼器的 decode() 方法,而這裡就是是直接呼叫 ByteBuf 的 readInt() 方法,即 ReplayingDecoderByteBuf 的 readInt() 方法;裡面會先判斷可讀位元組大小是否大於 4,如果大於則讀取,否則丟擲 Signal 這個 Error 型別的異常。
- 如果 ReplayingDecoder 捕捉 Signal 這個異常,會先判斷 checkpoint(即讀指標下標不) 是否為零,如果不是則重新設定讀指標下標,然後跳出讀迴圈。
ReplayingDecoder 能做到自主控制解碼的時機,是因為使用 ReplayingDecoderByteBuf 對 ByteBuf 進行修飾,在呼叫 ByteBuf 的方法前,會先呼叫自己的判斷邏輯,這也就是我們常說的裝飾器模式。
三、裝飾器模式的特點
首先,被裝飾的類和裝飾類都是繼承同一個類(抽象類)或實現同一個介面。
接著,被裝飾類會作為裝飾類的成員變數。
最後,在執行被裝飾類的方法前後,可能會呼叫裝飾類的方法。
場景總結:
裝飾器模式常用於這麼一個場景:在不修改類的狀態(屬性或行為)下,對類的功能進行擴充套件!
當然啦,這是我自己個人的總結,大家可去閱讀專業的書籍來證實這是否正確。如果有更好的總結,可以留言給我,讓我也學習學習~