天天看點

python 多裝飾器在類上_靈活又強大的裝飾器模式執行個體與應用源碼解析

裝飾器模式,又稱為包裝模式,是一種結構型模式。這種設計模式是指能夠在一個類的基礎上增加一個裝飾類(也可以叫包裝類),并在裝飾類中增加一些新的特性和功能。這樣,通過對原有類的包裝,就可以在不改變原有類的情況下為原有類增加更多的功能。

例如我們定義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.");
           

運作結果如圖所示。

python 多裝飾器在類上_靈活又強大的裝飾器模式執行個體與應用源碼解析

本示例中,我們使用裝飾器模式對被包裝類的功能進行了擴充,但是不影響原有類。遵照這個思想,還可以通過包裝類增加新的方法、屬性等。例如,我們給原來的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接口的子類中,隻有一個實作類,但卻有十個裝飾器類。通過使用不同的裝飾器裝飾實作類可以讓實作類有着不同的功能。

python 多裝飾器在類上_靈活又強大的裝飾器模式執行個體與應用源碼解析

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的源碼為執行個體講述源碼閱讀方法的書籍,總結了大量的程式設計知識和架構經驗,對提升程式設計和架構能力十分有用,非常推薦。

python 多裝飾器在類上_靈活又強大的裝飾器模式執行個體與應用源碼解析

書中對于Mybatis的各種緩存裝飾器的實作源碼都進行了介紹,看完就覺着,用好裝飾器真是——靈活又強大!

最後,我是進階軟體架構師易哥。

歡迎關注我,我會偶爾分享軟體架構和程式設計方面的知識。