有些函數,在各個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() 函數,再次編譯並測試: