裝飾器模式,又稱為包裝模式,是一種結構型模式。這種設計模式是指能夠在一個類的基礎上增加一個裝飾類(也可以叫包裝類),并在裝飾類中增加一些新的特性和功能。這樣,通過對原有類的包裝,就可以在不改變原有類的情況下為原有類增加更多的功能。
例如我們定義Phone接口,它規定了發送和接收語音的抽象方法。
public interface Phone { String callIn(); Boolean callOut(String info);}
然後定義一個類TelePhone,實作了Phone接口,能夠實作打電話的功能。
public class TelePhone implements Phone { @Override public String callIn() { System.out.println("接受語音……"); return "get info"; } @Override public Boolean callOut(String info) { System.out.println("發送語音:" + info); return true; }}
那現在我們要建立一個裝飾器,在不改變原有TelePhone的基礎上,實作通話錄音功能。我們的裝飾器類的源碼如下所示。
public class PhoneRecordDecorator implements Phone { private Phone decoratedPhone; public PhoneRecordDecorator(Phone decoratedPhone) { this.decoratedPhone = decoratedPhone; } @Override public String callIn() { System.out.println("啟動錄音……"); String info = decoratedPhone.callIn(); System.out.println("結束錄音并儲存錄音檔案。"); return info; } @Override public Boolean callOut(String info) { System.out.println("啟動錄音……"); Boolean result = decoratedPhone.callOut(info); System.out.println("結束錄音并儲存錄音檔案。"); return result; }}
這樣,經過我們的PhoneRecordDecorator包裝過的Phone就具有了通話錄音的能力,如下所示。
System.out.println("--原有Phone無錄音功能--");Phone phone = new TelePhone();phone.callOut("Hello, this is yee.");System.out.println();System.out.println("--經過裝飾後的Phone有錄音功能--");Phone phoneWithRecorder = new PhoneRecordDecorator(phone);phoneWithRecorder.callOut("Hello, this is yee.");
運作結果如圖所示。
本示例中,我們使用裝飾器模式對被包裝類的功能進行了擴充,但是不影響原有類。遵照這個思想,還可以通過包裝類增加新的方法、屬性等。例如,我們給原來的TelePhone類增加收發短信功能,如下所示。
public class PhoneMessageDecorator implements Phone { private Phone decoratedPhone; public PhoneMessageDecorator(Phone decoratedPhone) { this.decoratedPhone = decoratedPhone; } @Override public String callIn() { return decoratedPhone.callIn(); } @Override public Boolean callOut(String info) { return decoratedPhone.callOut(info); } public String receiveMessage() { // 省略接受短信操作 return "receive message"; } public Boolean sendMessage(String info) { // 省略發送短信操作 return true; }}
裝飾器模式在程式設計開發中經常使用。通常的使用場景是在一個核心基本類的基礎上,提供大量的裝飾器,進而使得核心基本類經過不同的裝飾器修飾後獲得不同的功能。(均參考自《通用源碼閱讀指導書——MyBatis源碼詳解》)
裝飾器還有一個優點就是可以疊加使用,即一個核心基本類可以被多個裝飾器修飾,進而同時具有這多個裝飾器的功能。
MyBatis便使用了大量的裝飾器實作了不同類型的緩存,它們都在cache包中。在imple子包中存放了實作類,在decorators子包中存放了衆多裝飾器類。而Cache接口是實作類和裝飾器類的共同接口。
下面給出了Cache接口及其子類的類圖。Cache接口的子類中,隻有一個實作類,但卻有十個裝飾器類。通過使用不同的裝飾器裝飾實作類可以讓實作類有着不同的功能。
Cache接口的源碼如下所示,在接口中定義了實作類和裝飾器類中必須實作的方法。
public interface Cache { /** * 擷取緩存id * @return 緩存id */ String getId(); /** * 向緩存寫入一條資料 * @param key 資料的鍵 * @param value 資料的值 */ void putObject(Object key, Object value); /** * 從緩存中讀取一條資料 * @param key 資料的鍵 * @return 資料的值 */ Object getObject(Object key); /** * 從緩存中删除一條資料 * @param key 資料的鍵 * @return 原來的資料值 */ Object removeObject(Object key); /** * 清空緩存 */ void clear(); /** * 讀取緩存中資料的數目 * @return 資料的數目 */ int getSize(); /** * 擷取讀寫鎖,該方法已經廢棄 * @return 讀寫鎖 */ default ReadWriteLock getReadWriteLock() { return null; }}
緩存實作類PerpetualCache的實作非常簡單,但可以通過裝飾器來為其增加更多的功能。decorators子包中存在許多裝飾器,根據裝飾器的功能可以将它們可以分為以下幾個大類:
- 同步裝飾器:為緩存增加同步功能,如SynchronizedCache類。
- 日志裝飾器:為緩存增加日志功能,如LoggingCache類。
- 清理政策裝飾器:為緩存中的資料增加清理功能,如FifoCache類、LruCache類、WeakCache類、SoftCache類。
- 阻塞裝飾器:為緩存增加阻塞功能,如BlockingCache類。
- 重新整理裝飾器:為緩存增加定時重新整理功能,如ScheduledCache類。
- 序列化裝飾器:為緩存增加序列化功能,如SerializedCache類。
- 事務裝飾器:用于支援事務操作的裝飾器,如TransactionalCache類。
《通用源碼閱讀指導書——MyBatis源碼詳解》一書中對于上述裝飾器的實作進行了詳細的介紹,我們不再一一展開。僅拿出做簡要介紹。
FifoCache類是一個裝飾器,經過它裝飾的緩存會采用先進先出的政策來清理緩存,它内部使用了keyList屬性存儲了緩存資料的寫入順序,并且使用size屬性存儲了緩存資料的數量限制。當緩存中的資料達到限制時,FifoCache裝飾器會将最先放入緩存中的資料删除。下面展示了FifoCache類的屬性。
// 被裝飾對象private final Cache delegate;// 按照寫入順序儲存了緩存資料的鍵private final Deque keyList;// 緩存空間的大小private int size;
當向緩存中存入資料時,FifoCache類會判斷資料數量是否已經超過限制。如果超過,則會将最先寫入緩存的資料删除,下面展示了相關操作的源碼。
/** * 向緩存寫入一條資料 * @param key 資料的鍵 * @param value 資料的值 */@Overridepublic void putObject(Object key, Object value) { cycleKeyList(key); delegate.putObject(key, value);}/** * 記錄目前放入的資料的鍵,同時根據空間設定清除超出的資料 * @param key 目前放入的資料的鍵 */private void cycleKeyList(Object key) { keyList.addLast(key); if (keyList.size() > size) { Object oldestKey = keyList.removeFirst(); delegate.removeObject(oldestKey); }}
其中的delegate引用的就是緩存最終的實作類,當然也可能是被裝飾器裝飾後的實作類。畢竟,我們也說過,裝飾器可以疊加使用。
關于其他各種緩存裝飾器的實作、裝飾器的疊加使用等等,大家可以參考《通用源碼閱讀指導書——MyBatis源碼詳解》。這是一本以MyBatis的源碼為執行個體講述源碼閱讀方法的書籍,總結了大量的程式設計知識和架構經驗,對提升程式設計和架構能力十分有用,非常推薦。
書中對于Mybatis的各種緩存裝飾器的實作源碼都進行了介紹,看完就覺着,用好裝飾器真是——靈活又強大!
最後,我是進階軟體架構師易哥。
歡迎關注我,我會偶爾分享軟體架構和程式設計方面的知識。