Introduce Local Extension (引入本地擴展)

Summary:

你須要爲服務類提供一些額外函數,但你沒法修改這個類。創建一個新類,使它包含這些額外函數。讓這個擴展品成爲源類的子類或包裝類。 java

Motivation: 

在使用一些沒法修改源碼的類時,須要添加一些原類沒有提供的函數,而這些函數又比較多,那麼咱們須要將這些函數組織在一塊兒放到恰當的地方。要達到這一目的,有兩種標準對象技術能夠用—子類化(subclassing)和包裝(wrapping),這種狀況下,咱們把子類和包裝類統稱爲本地擴展(local extension)。 數組

使用本地擴展,使得「函數和數據應該被統一封裝」這一原則得以堅持。若是一直把這些本該放在擴展類中的代碼零散的置於其餘函數中,最終只會讓其餘這些類變得過度複雜,並是的其中的函數難以複用。 app

在子類和包裝類之間作選擇時,首選子類,由於這樣的工做量比較少。使用子類最大的障礙在於,它必須在對象建立期實施。若是咱們能夠接管對象建立過程,那固然沒問題;但若是想在對象建立以後再使用本地擴展,就有問題了。此外,子類化方案還必須產生一個子類對象,這種狀況下,若是有其餘對象引用了舊對象,咱們就同時有兩個對象保存了原數據!若是原數據是不可修改的,那也沒問題,咱們能夠放心進行復制;但若是原數據容許被修改,問題就來了,由於一個修改動做沒法同時改變兩份副本。這是咱們就必須改用包裝類。使用包裝類時,對本地擴展的修改會搏擊原對象,反之亦然。 函數

Mechanics:

 1.創建一個擴展類,將它做爲原始類的子類或包裝類。 this

2.在擴展類中加入轉型構造函數。 spa

所謂「轉型構造函數」是指「接受原對象做爲參數」的構造函數。若是採用子類化方案,那麼轉型構造函數應該調用適當的超類構造函數;若是採用包裝類方案,那麼轉型構造函數應該將它獲得的傳入參數以實例變量的形式保存起來,用做接受委託的原對象。 code

3.在擴展類中加入新特性。 對象

4.根據須要,將原對象替換爲擴展對象。 get

5.將針對原始類定義的全部外加函數搬移到擴展類中。 源碼

範例

以java1.0.1的Date類爲例。第一項待解決事項就是:使用子類仍是包裝類。子類化是比較顯而易見的辦法:

class myDateSub extends Date{
     public myDateSub nextDay() ...
     public int dayOfYear() ...
}

包裝類則需用上委託:

class myDateWrap{
      private Date original;
}
範例1:使用子類

        首先,創建一個myDateSub類表示「日期」,並使其成爲Date的子類:

class myDateSub extends Date

而後,處理Date和擴展類的不一樣處。MyDateSub構造函數須要委託給Date構造函數:

public MyDateSub(String dateString)
{
     super(dateString);
}

如今,加入一個轉型構造函數,其參數是一個源類對象:

public MyDateSub(Date arg)
{
      super(arg.getTime);
}

如今,在擴展類中添加新特性,並使用Move Method將全部外加函數搬移到擴展類。因而下面代碼:

client class...
     private static Date nextDay(Date arg)
     {
         //foreign method, should be on Date
          return new Date(arg.getYear(),arg.getMonth(),arg.getDate()+1)
     }

通過搬移以後,就成了:

class myDateSub...
     Date nextDay()
     {
         return new Date(getYear(),getMonth(),getDate()+1)
     }
範例2: 使用包裝類

首先聲明一個包裝類:

class MyDateWrap{
    private Date original;
}

使用包專類方案是,對構造函數的設定與先前有所不一樣。如今的構造函數將執行一個單純的委託動做

public MyDateWrap(String dateString)
{
   original = new Date(dateString);
}

而轉型構造函數則只是對其實例變量賦值而已:

public MyDateWrap(Date arg)
{
    original = arg;
}

接下來是一項枯燥乏味的工做:爲原始類的全部函數提供委託函數。

public int getYear()
{
  return original.getYear();
}
public boolean equals(Object arg)
{
    if(this == arg)
    {
       return true;
    }
    if(!(arg instanceof MyDateWrap))
    {
       return false;
    }
    MyDateWrap other = ((MyDateWrap)arg);
    return (original.equale(other.original));
}

完成這項工做後,就能夠使用Move Method將日期相關行爲搬移到新類中。因而一下代碼:

client class...
   private static Date nextDay(Date arg)
   {
      // foreign method, should be on Date
      return new Date(arg.getYear(),arg.getMonth(),arg.getDate()+1);
   }

通過搬移以後,就成了:

class MyDateWrap...
   Date nextDay
   {
     return new Date(getYear(),getMonth(),getDate()+1);
   }

使用包裝類有一個特殊問題:如何處理「接受原始類之實例爲參數」的函數。例如:

public boolean after(Date arg)

因爲沒法改變原始類,因此咱們只能作到在一個方向上的兼容--包裝類的after() 函數能夠接受包裝類或原始類的對象;但原始類只能接受原始類的對象,不接受包裝類對象。

這樣覆寫的目的是爲了向用戶隱藏包裝類的存在。可是咱們沒法徹底隱藏包裝類的存在,由於某些系統所提供的函數(例如equals())會出問題。

在這種狀況下,咱們只能向用戶公開「我進行了包裝」這一事實。咱們能夠以一個新函數來進行日期之間的相等性檢查:

public boolean equalsDate(Date arg);

能夠重載equalsDate(),讓一個重載版本接受Date對象,另外一個重載版本接受MyDateWrap對象。這樣就沒必要檢查未知對象的類型了:

public boolean quealsDate(MyDateWrap arg);

子類化方案中就沒有這樣的問題,只要不覆寫源函數就好了。

相關文章
相關標籤/搜索