串聯建構函式

Chain Constructors

Joshua Kerievsky               透明

 

你擁有多個建構函式,其中包含了重複的程式碼。

將建構函式串在一起,以使重複的程式碼減到最少。

 

public class Loan {

...

public Loan(float notional, float outstanding, int rating, Date expiry) {

this.strategy = new TermROC();

this.notional = notional;

this.outstanding = outstanding;

this.rating =rating;

this.expiry = expiry;

}

public Loan(float notional, float outstanding, int rating, Date expiry, Date maturity) {

this.strategy = new RevolvingTermROC();

this.notional = notional;

this.outstanding = outstanding;

this.rating = rating;

this.expiry = expiry;

this.maturity = maturity;

          }

public Loan(CapitalStrategy strategy, float notional, float outstanding,

int rating, Date expiry, Date maturity) {

this.strategy = strategy;

this.notional = notional;

this.outstanding = outstanding;

this.rating = rating;

this.expiry = expiry;

this.maturity = maturity;

}

}

 

 

public class Loan {

...

public Loan(float notional, float outstanding, int rating, Date expiry) {

this(new TermROC(), notional, outstanding, rating, expiry, null);

}

public Loan(float notional, float outstanding, int rating, Date expiry, Date maturity) {

this(new RevolvingTermROC(), notional, outstanding, rating, expiry, maturity);

}

public Loan(CapitalStrategy strategy, float notional, float outstanding,

int rating, Date expiry, Date maturity) {

this.strategy = strategy;

this.notional = notional;

this.outstanding = outstanding;

this.rating = rating;

this.expiry = expiry;

this.maturity = maturity;

}

}

 

動機

        在同一個類的兩個或更多的建構函式中編寫重複程式碼,這就是在為自己埋下麻煩的種子。別人會在你的類中添加新的變數,然後更新一個建構函式來對這個變數進行初始化,但是卻忘了更新別的建構函式。於是,“砰”的一聲,向新的 bug 問好吧。一個類中的建構函式越多,程式碼的重複就會傷害你越重。如果有可能,就應該儘量減少或去除程式碼重複,這樣做的額外好處就是可以幫助你的程式碼系統減肥
 

        為了達到這個目標,我們經常會使用Constructor Chaining式進行重整:特化的(specific)建構函式呼叫普化的(general-purposed)建構函式,重複這個過程,直到最普化的建構函式也被呼叫到。如果你的每條呼叫鏈的末端都是同一個建構函式,我就把它叫做 “catch-all” 建構函式,因為它處理了所有建構函式的呼叫。這個 catch-all 建構函式通常會比其他建構函式接受更多的參數,並且可能是(也可能不是)私有的或保護的。
 

        如果你發現多個建構函式降低了類的可用性,請考慮使用“用 Factory Method 模式替換多個建構函式”的重方法。

 

溝通

重複

簡化

如果一個類中的多個建構函式實重複的工作,那麼在特化與普化的溝通上,你的程式碼就是失敗的。要實這種溝通,就應該讓特化的建構函式呼叫普化的,並讓每個建構函式都做自己獨一無二的工作。

建構函式中的重複程式碼讓你的類更容易出錯、更難維護。尋找通用的功能,將它放到普化的建構函式中,將呼叫轉發給這些普化的建構函式,並在其他的建構函式中實現用途不廣泛的功能。

如果超過一個的建構函式包含同樣的程式碼,要看出建構函式之間的區別就很困難了。讓特化的建構函式呼叫普化的,並形成一條呼叫鏈,從而對建構函式進行簡化。

 

技巧

1.   尋找包含重複程式碼的兩個建構函式(我把它們分別叫做 A B)。確定 A 是否可以呼叫 B 或者 B 是否可以呼叫 A,這樣重複程式碼才可以被安全的(和比較容易的)從這某一個建構函式中刪掉。

2.   編譯、測試。

3.   對類中的每個建構函式,重複步驟 1 2,以獲得儘量少的重複程式碼。

4.   如果某個建構函式不必要成為公開的,就改變它的可見性。

5.   編譯、測試。

範例

1.   我們將從一段簡單程式碼——一個 Loan——開始。這個類有三個建構函式,用來表現三種不同類型的貸款業務。大量醜陋的重複程式碼。

public Loan(float notional, float outstanding, int rating, Date expiry) {

this.strategy = new TermROC();

this.notional = notional;

this.outstanding = outstanding;

this.rating = rating;

this.expiry = expiry;

}

public Loan(float notional, float outstanding, int rating, Date expiry, Date maturity) {

this.strategy = new RevolvingTermROC();

this.notional = notional;

this.outstanding = outstanding;

this.rating = rating;

this.expiry = expiry;

this.maturity = maturity;

}

public Loan(CapitalStrategy strategy, float notional, float outstanding, int rating,

Date expiry, Date maturity) {

this.strategy = strategy;

this.notional = notional;

this.outstanding = outstanding;

this.rating = rating;

this.expiry = expiry;

this.maturity = maturity;

}

       我研究了前面的兩個建構函式。它們包含重複的程式碼,而第三個建構函式也同樣。我考慮第一個建構函式應該呼叫哪一個,並發現它應該呼叫第三個建構函式。因此我把第一個建構函式修改成:

public Loan(float notional, float outstanding, int rating, Date expiry) {

this(new TermROC(), notional, outstanding, rating, expiry, null);

}

2.   編譯程序,測試這次修改是否正常工作。

3.   重複步驟 1 2,儘量去除程式碼重複。這引出了第二個建構函式,它也呼叫第三個建構函式,如下所示:

public Loan(float notional, float outstanding, int rating, Date expiry, Date maturity) {

this(new RevolvingTermROC(), notional, outstanding, rating, expiry, maturity);

}

現在我知道第三個建構函式就是我的 catch-all 建構函式,因為它處理了所有的建構細節。

4.   檢查這三個建構函式所有的呼叫者,以確定是否可以改變某一個的可見性。在這個案例中,我不能這樣做(假設是這樣——在這塈A無法知道其他程式碼的情況)。

5.   編譯並測試,完成此次重整。

 

譯者的話

      在我學習設計模式的過程中,曾經有這樣一種感覺:我知道相當多的設計式,但是很多模式我不知道應該如何去使用。Christopher Alexander 曾經說過:式是在特定場景下解決特定問題的可重複的解決方案[Alex77]。可是我常常感覺不到這種場景。
 

      在《設計式》一書中,GoF 曾經說過:設計重整的目標[GoF95]。這為設計式的學習者提供了一種著眼點。Martin Fowler 也列舉出了很多重整方法,其中一些方法就是以設計模式為目標的——重整的結果就是設計式的結構[Fowler99]
 

     本文的作者也是一位軟體發展的專家。他在 Martin Fowler 的研究基礎上發展出了更多的以式為目標的重整技術,本文就是其中之一。這一系列文章,多以《設計式》中的式為目標,因此讀來更具系統性和連續性。而他行文的風格和重整的描述格式又基本與 Martin Fowler 一致,也算簡單易懂。我想這些文章應該對設計式的學習者有一定幫助。
 

     本系列文章以及 http://industriallogic.com/xp/refactoring/ 頁面下所有文章的中譯本一切權利由 Kerievsky 先生授權給我,任何人如果要將這些文章用於任何目的,請通知我或UMLChina

 

參考書目

[Alex77]  Alexander, C., Ishikawa, S., Silverstein, M., A Pattern Language, New York: Oxford University Press, 1977.

 

[Fowler99]   Fowler, M., Beck, K., Brant, J., Opdyke, W., Roberts, D., Refactoring: Improving the Design of Existing Code, Reading Mass.: Addison-Wesley, 1999.

 

[GoF95]  Gamma, E., Heml, R., Johnson, R., Vlissides, J., Design Patterns: Elements of Reusable Object-Oriented Software, Reading Mass.: Addison-Wesley, 1995. 中譯本:《設計模式:可複用面向物件軟體的基礎》,李英軍等譯,機械工業出版社,20009月。(校註:前面所列的中譯本為簡體中文版,繁體中文版為《物件導向設計模式》,葉秉哲譯,培生出版。)