處理概括關係之二 :Pull Up Method(函數上移)

有些函數,在各個subclass 中產生完全相同的結果。

將該函數移至superclass。

動機(Motivation)

避免「行爲重複」是很重要的。儘管「重複的兩個函數」也可以各自工作得很好, 但「重複」自身會成爲錯誤的滋生地,此外別無價值。無論何時,只要系統之內出現重複,你就會面臨「修改其中一個卻未能修改另一個」的風險。通常,找出重複也有一定困難。

如果某個函數在各subclass 中的函數體都相同(它們很可能是通過「拷貝-粘貼」得到的),這就是最顯而易見的Pull Up Method 適用場合。當然,情況並不總是如此明顯。你也可以只管放心地重構,再看看測試程序會不會發牢騷,但這就需要對你的測試有充分的信心。我發現,觀察這些可疑(可能重複的〕函數之間的差異往往大有收穫:它們經常會向我展示那些我忘記測試的行爲。

Pull Up Method 常常緊隨其他重構而被使用。也許你能找出若干個「身處不 同subclasses 內的函數」而它們又可以「通過某種形式的參數調整」而後成爲相同函數。這時候,最簡單的辦法就是首先分別調整這些函數的參數,然後再將它們概括(generalize)到superclass中。當然,如果你自信足夠,也可以一次同時完成這兩個步驟。

有一種特殊情況也需要使用Pull Up Method : subclass 的函數覆寫(overrides) 了superclass 的函數,但卻仍然做相同的工作。

Pull Up Method 過程中最麻煩的一點就是:被提升的函數可能會引用「只出現於subclass 而不出現於superclass」的特性。如果被引用的是個函數,你可以將該函數也一同提升到superclass,或者在superclass 中建立一個抽象函數。在此過程中,你可能需要修改某個函數的簽名式(signature),或建立一個委託函數(delegating method)。

如果兩個函數相似但不相同,你或許可以先以Form Template Method 構造出相同的函數,然後再提升它們。

作法(Mechanics)

· 檢查「待提升函數」,確定它們是完全一致的(identical)。
Ø 如果這些函數看上去做了相同的事,但並不完全一致,可使用Substitute Algorithm 讓它們變得完全一致。

· 如果「待提升函數」的簽名式(signature)不同,將那些簽名式都修改爲你想要在superclass 中使用的簽名式。

· 在superclass 中新建一個函數,將某一個「待提升函數」的代碼拷貝到其中,做適當調整,然後編譯。

Ø 如果你使用的是一種強型(strongly typed)語言,而「待提升函數」 又調用了一個「只出現於subclass 未出現於superclass」的函數,你可以在superclass 中爲被調用函數聲明一個抽象函數。

Ø 如果「待提升函數」使用了 subclass 的一個值域,你可以使用Pull Up Field 將該值域也提升到superclass;或者也可以先使用 Self Encapsulate Field,然後在superclass 中把取值函數(getter)聲明爲抽象函數。

· 移除一個「待提升的subclass 函數」。

· 編譯,測試。

· 逐一移除「待提升的如函數」,直到只剩下superclass 中的函數爲止。每次移除之後都需要測試。

· 觀察該函數的調用者,看看是否可以將它所索求的對象型別改爲superclass。

範例:(Example)

我以Customer「表示「顧客」,它有兩個subclass  :表示「普通顧客」的RegularCustomer 和表示「貴賓」PreferredCustomer。

兩個subclass 都有一個createBill() 函數,並且代碼完全一樣:

void createBill (date Date) {

   double chargeAmount = charge (lastBillDate, date);

   addBill (date, charge);

}

但我不能直接把這個函數上移到superclass,因爲各個subclass 的chargeFor() 函數並不相同。我必須先在superclass 中聲明chargeFor()  抽象函數:

class Customer...

   abstract double chargeFor(date start, date end)

然後,我就可以將createBill()  函數從其中一個subclass 拷貝到superclass。拷貝完之後應該編譯,然後移除那個subclass 的createBill() 函數,然後編譯並測試。 隨後再移除另一個subclass 的createBill() 函數,再次編譯並測試:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章