### Form Template Method(塑造模板函數)
你有一些subclasses ,其中相應的某些函數以相同順序執行類似的措施,但各措施實際上有所不同。
將各個措施分別放進獨立函數中,并保持它們都有相同的簽名式(signature),于是原函數也就變得相同了。然后將原函數上移至superclass 。

**動機(Motivation)**
繼承是「避免重復行為」的一個強大工具。無論何時,只要你看見兩個subclasses 之中有類似的函數,就可以把它們提升到superclass 。但是如果這些函數并不完全相同呢?此時的你應該怎么辦?我們仍有必要盡量避免重復,但又必須保持這些函 數之間的實質差異。
常見的一種情況是:兩個函數以相同序列(sequence)執行大致相近的措施,但是各措施不完全相同。這種情況下我們可以將「執行各措施」的序列移至superclass , 并倚賴多態(polymorphism )保證各措施仍得以保持差異性。這樣的函數被稱為Template Method (模板函數)[Gang of Four]。
**作法(Mechanics)**
- 在各個subclass 中分解目標函數,使分解后的各個函數要不完全相同,要不完全不同。
- 運用Pull Up Method 將各subclass 內寒全相同的函數上移至superclass 。
- 對于那些(剩余的、存在于各subclasses 內的)完全不同的函數,實施Rename Method,使所有這些函數的簽名式(signature)完全相同。
- 這將使得原函數變為完全相同,因為它們都執行同樣一組函數調用; 但各subclass 會以不同方式響應這些調用。
- 修改上述所有簽名式后,編譯并測試。
- 運用Pull Up Method 將所有原函數上移至superclass 。在superclass 中將那些「有所不同、代表各種不同措施」的函數定義為抽象函數。
- 編譯,測試。
- 移除其他subclass 中的原函數,每刪除一個,編譯并測試。
**范例:(Example)**
現在我將完成第一章遺留的那個范例。在此范例中,我有一個Customer ,其中有兩個用于打印的函數。statement() 函數以ASCII 碼打印報表(statement):
~~~
public String statement() {
Enumeration rentals = _rentals.elements();
String result = "Rental Record for " + getName() + "\n";
while (rentals.hasMoreElements()) {
Rental each = (Rental) rentals.nextElement();
//show figures for this rental
result += "\t" + each.getMovie().getTitle()+ "\t" +
String.valueOf(each.getCharge()) + "\n";
}
//add footer lines
result += "Amount owed is " + String.valueOf(getTotalCharge()) + "\n";
result += "You earned " + String.valueOf(getTotalFrequentRenterPoints()) +
" frequent renter points";
return result;
}
~~~
函數htmlStatement() 則以HTML 格式輸出報表:
~~~
public String htmlStatement() {
Enumeration rentals = _rentals.elements();
String result = "<H1>Rentals for <EM>" + getName() + "</EM></H1><P>\n";
while (rentals.hasMoreElements()) {
Rental each = (Rental) rentals.nextElement();
//show figures for each rental
result += each.getMovie().getTitle()+ ": " +
String.valueOf(each.getCharge()) + "<BR>\n";
}
//add footer lines
result += "<P>You owe <EM>" + String.valueOf(getTotalCharge()) + "</EM><P>\n";
result += "On this rental you earned <EM>" +
String.valueOf(getTotalFrequentRenterPoints()) +
"</EM> frequent renter points<P>";
return result;
}
~~~
使用 Form Template Method 之前,我需要對上述兩個函數做一些整理,使它們成為「某個共同superclass 」下的subclass 函數。為了這一目的,我使用函數對象(method object)[Beck] 針對「報表打印工作」創建一個「獨立的策略繼承體系」(separate strategy hierarchy ),如圖11.1。

圖11.1 針對「報表輸出」使用Stategy 模式
~~~
class Statement {}
class TextStatement extends Statement {}
class HtmlStatement extends Statement {}
~~~
現在,通過Move Method,我將兩個負責輸出報表的函數分別搬移到對應的subclass 中:
~~~
class Customer...
public String statement() {
return new TextStatement().value(this);
}
public String htmlStatement() {
return new HtmlStatement().value(this);
}
class TextStatement {
public String value(Customer aCustomer) {
Enumeration rentals = aCustomer.getRentals();
String result = "Rental Record for " + aCustomer.getName() + "\n";
while (rentals.hasMoreElements()) {
Rental each = (Rental) rentals.nextElement();
//show figures for this rental
result += "\t" + each.getMovie().getTitle()+ "\t" +
String.valueOf(each.getCharge()) + "\n";
}
//add footer lines
result += "Amount owed is " + String.valueOf(aCustomer.getTotalCharge()) + "\n";
result += "You earned " + String.valueOf(aCustomer.getTotalFrequentRenterPoints()) +
" frequent renter points";
return result;
}
class HtmlStatement {
public String value(Customer aCustomer) {
Enumeration rentals = aCustomer.getRentals();
String result = "<H1>Rentals for <EM>" + aCustomer.getName() + "</EM></H1><P>\n";
while (rentals.hasMoreElements()) {
Rental each = (Rental) rentals.nextElement();
//show figures for each rental
result += each.getMovie().getTitle()+ ": " +
String.valueOf(each.getCharge()) + "<BR>\n";
}
//add footer lines
result += "<P>You owe <EM>" + String.valueOf(aCustomer.getTotalCharge()) +
"</EM><P>\n";
result += "On this rental you earned <EM>"
String.valueOf(aCustomer.getTotalFrequentRenterPoints()) +
"</EM> frequent renter points<P>";
return result;
}
~~~
搬移之后,我還對這兩個函數的名稱做了一些修改,使它們更好地適應Strategy 模式的要求。我之所以為它們取相同名稱,因為兩者之間的差異不在于函數,而在于函數所屬的class 。如果你想試著編譯這段代碼,還必須在Customer class 中添加一個getRentals() 函數,并放寬getTotalCharge() 函數和getTotalFrequentRenterPoints() 函數的可視性(visibility )。
面對兩個subclass 中的相似函數,我可以開始實施Form Template Method 了。本重構的關鍵在于:運用 Extract Method 將兩個函數的不同部分提煉出 來,從而將相像的代碼(similar code)和變動的代碼( varying code )分開。每次提煉后,我就建立一個簽名式(signature)相同但本體(bodies)不同的函數。
第一個例子就是打印報表表頭(headers)。上述兩個函數都通過Customer 對象獲取信息,但對運算結果(字符串)的格式化方式不同。我可以將「對字符串的格式化動作」提煉到獨立函數中,并將提煉所得命以相同的簽名式(signature):
~~~
class TextStatement...
String headerString(Customer aCustomer) {
return "Rental Record for " + aCustomer.getName() + "\n";
}
public String value(Customer aCustomer) {
Enumeration rentals = aCustomer.getRentals();
String result =headerString(aCustomer);
while (rentals.hasMoreElements()) {
Rental each = (Rental) rentals.nextElement();
//show figures for this rental
result += "\t" + each.getMovie().getTitle()+ "\t" +
String.valueOf(each.getCharge()) + "\n";
}
//add footer lines
result += "Amount owed is " + String.valueOf(aCustomer.getTotalCharge()) + "\n";
result += "You earned " + String.valueOf(aCustomer.getTotalFrequentRenterPoints()) +
" frequent renter points";
return result;
}
class HtmlStatement...
String headerString(Customer aCustomer) {
return "<H1>Rentals for <EM>" + aCustomer.getName() + "</EM></H1><P>\n";
}
public String value(Customer aCustomer) {
Enumeration rentals = aCustomer.getRentals();
String result = headerString(aCustomer);
while (rentals.hasMoreElements()) {
Rental each = (Rental) rentals.nextElement();
//show figures for each rental
result += each.getMovie().getTitle()+ ": " +
String.valueOf(each.getCharge()) + "<BR>\n";
}
//add footer lines
result += "<P>You owe <EM>" + String.valueOf(aCustomer.getTotalCharge()) + "</ EM><P>\n";
result += "On this rental you earned <EM>" +
String.valueOf(aCustomer.getTotalFrequentRenterPoints()) +
"</EM> frequent renter points<P>";
return result;
}
~~~
編譯并測試,然后繼續處理其他元素。我將逐一對各個元素進行上述過程。下面是整個重構完成后的結果:
~~~
class TextStatement …
public String value(Customer aCustomer) {
Enumeration rentals = aCustomer.getRentals();
String result = headerString(aCustomer);
while (rentals.hasMoreElements()) {
Rental each = (Rental) rentals.nextElement();
result += eachRentalString(each);
}
result += footerString(aCustomer);
return result;
}
String eachRentalString (Rental aRental) {
return "\t" + aRental.getMovie().getTitle()+ "\t" +
String.valueOf(aRental.getCharge()) + "\n";
}
String footerString (Customer aCustomer) {
return "Amount owed is " + String.valueOf(aCustomer.getTotalCharge()) + "\n" +
"You earned " + String.valueOf(aCustomer.getTotalFrequentRenterPoints()) +
" frequent renter points";
}
class HtmlStatement…
public String value(Customer aCustomer) {
Enumeration rentals = aCustomer.getRentals();
String result = headerString(aCustomer);
while (rentals.hasMoreElements()) {
Rental each = (Rental) rentals.nextElement();
result += eachRentalString(each);
}
result += footerString(aCustomer);
return result;
}
String eachRentalString (Rental aRental) {
return aRental.getMovie().getTitle()+ ": " +
String.valueOf(aRental.getCharge()) + "<BR>\n";
}
String footerString (Customer aCustomer) {
return "<P>You owe <EM>" + String.valueOf(aCustomer.getTotalCharge()) +
"</EM><P>" + "On this rental you earned <EM>" +
String.valueOf(aCustomer.getTotalFrequentRenterPoints()) +
"</EM> frequent renter points<P>";
}
~~~
所有這些修改都完成后,兩個value() 函數看上去已經非常相似了,因此我可以使用Pull Up Method 將它們提升到superclass 中。提升完畢后,我需要在superclass 中把subclass 函數聲明為抽象函數。
~~~
class Statement...
public String value(Customer aCustomer) {
Enumeration rentals = aCustomer.getRentals();
String result = headerString(aCustomer);
while (rentals.hasMoreElements()) {
Rental each = (Rental) rentals.nextElement();
result += eachRentalString(each);
}
result += footerString(aCustomer);
return result;
}
abstract String headerString(Customer aCustomer);
abstract String eachRentalString (Rental aRental);
abstract String footerString (Customer aCustomer);
~~~
然后我把TextStatement.value() 函數拿掉,編譯并測試。完成之后再把HtmlStatement.value() 也刪掉,再次編譯并測試。最后結果如圖11.2。
完成本重構后,處理其他種類的報表就容易多了:你只需為Statement 再建一個subclass ,并在其中覆寫(overrides)三個抽象函數即可。

圖11.2 Templae Method(模板函數)塑造完畢后的classes
- 譯序 by 侯捷
- 譯序 by 熊節
- 序言
- 前言
- 章節一 重構,第一個案例
- 起點
- 重構的第一步
- 分解并重組statement()
- 運用多態(Polymorphism)取代與價格相關的條件邏輯
- 結語
- 章節二 重構原則
- 何謂重構
- 為何重構
- 「重構」助你找到臭蟲(bugs)
- 何時重構
- 怎么對經理說?
- 重構的難題
- 重構與設計
- 重構與性能(Performance)
- 重構起源何處?
- 章節三 代碼的壞味道
- Duplicated Code(重復的代碼)
- Long Method(過長函數)
- Large Class(過大類)
- Long Parameter List(過長參數列)
- Divergent Change(發散式變化)
- Shotgun Surgery(散彈式修改)
- Feature Envy(依戀情結)
- Data Clumps(數據泥團)
- Primitive Obsession(基本型別偏執)
- Switch Statements(switch驚悚現身)
- Parallel Inheritance Hierarchies(平行繼承體系)
- Lazy Class(冗贅類)
- Speculative Generality(夸夸其談未來性)
- Temporary Field(令人迷惑的暫時值域)
- Message Chains(過度耦合的消息鏈)
- Middle Man(中間轉手人)
- Inappropriate Intimacy(狎昵關系)
- Alternative Classes with Different Interfaces(異曲同工的類)
- Incomplete Library Class(不完美的程序庫類)
- Data Class(純稚的數據類)
- Refused Bequest(被拒絕的遺贈)
- Comments(過多的注釋)
- 章節四 構筑測試體系
- 自我測試代碼的價值
- JUnit測試框架
- 添加更多測試
- 章節五 重構名錄
- 重構的記錄格式
- 尋找引用點
- 這些重構準則有多成熟
- 章節六 重新組織你的函數
- Extract Method(提煉函數)
- Inline Method(將函數內聯化)
- Inline Temp(將臨時變量內聯化)
- Replace Temp with Query(以查詢取代臨時變量)
- Introduce Explaining Variable(引入解釋性變量)
- Split Temporary Variable(剖解臨時變量)
- Remove Assignments to Parameters(移除對參數的賦值動作)
- Replace Method with Method Object(以函數對象取代函數)
- Substitute Algorithm(替換你的算法)
- 章節七 在對象之間搬移特性
- Move Method(搬移函數)
- Move Field(搬移值域)
- Extract Class(提煉類)
- Inline Class(將類內聯化)
- Hide Delegate(隱藏「委托關系」)
- Remove Middle Man(移除中間人)
- Introduce Foreign Method(引入外加函數)
- Introduce Local Extension(引入本地擴展)
- 章節八 重新組織數據
- Self Encapsulate Field(自封裝值域)
- Replace Data Value with Object(以對象取代數據值)
- Change Value to Reference(將實值對象改為引用對象)
- Replace Array with Object(以對象取代數組)
- Replace Array with Object(以對象取代數組)
- Duplicate Observed Data(復制「被監視數據」)
- Change Unidirectional Association to Bidirectional(將單向關聯改為雙向)
- Change Bidirectional Association to Unidirectional(將雙向關聯改為單向)
- Replace Magic Number with Symbolic Constant(以符號常量/字面常量取代魔法數)
- Encapsulate Field(封裝值域)
- Encapsulate Collection(封裝群集)
- Replace Record with Data Class(以數據類取代記錄)
- Replace Type Code with Class(以類取代型別碼)
- Replace Type Code with Subclasses(以子類取代型別碼)
- Replace Type Code with State/Strategy(以State/strategy 取代型別碼)
- Replace Subclass with Fields(以值域取代子類)
- 章節九 簡化條件表達式
- Decompose Conditional(分解條件式)
- Consolidate Conditional Expression(合并條件式)
- Consolidate Duplicate Conditional Fragments(合并重復的條件片段)
- Remove Control Flag(移除控制標記)
- Replace Nested Conditional with Guard Clauses(以衛語句取代嵌套條件式)
- Replace Conditional with Polymorphism(以多態取代條件式)
- Introduce Null Object(引入Null 對象)
- Introduce Assertion(引入斷言)
- 章節十一 處理概括關系
- Pull Up Field(值域上移)
- Pull Up Method(函數上移)
- Pull Up Constructor Body(構造函數本體上移)
- Push Down Method(函數下移)
- Push Down Field(值域下移)
- Extract Subclass(提煉子類)
- Extract Superclass(提煉超類)
- Extract Interface(提煉接口)
- Collapse Hierarchy(折疊繼承關系)
- Form Template Method(塑造模板函數)
- Replace Inheritance with Delegation(以委托取代繼承)
- Replace Delegation with Inheritance(以繼承取代委托)
- 章節十二 大型重構
- 這場游戲的本質
- Tease Apart Inheritance(梳理并分解繼承體系)
- Convert Procedural Design to Objects(將過程化設計轉化為對象設計)
- Separate Domain from Presentation(將領域和表述/顯示分離)
- Extract Hierarchy(提煉繼承體系)
- 章節十三 重構,復用與現實
- 現實的檢驗
- 為什么開發者不愿意重構他們的程序?
- 現實的檢驗(再論)
- 重構的資源和參考資料
- 從重構聯想到軟件復用和技術傳播
- 結語
- 參考文獻
- 章節十四 重構工具
- 使用工具進行重構
- 重構工具的技術標準(Technical Criteria )
- 重構工具的實用標準(Practical Criteria )
- 小結
- 章節十五 集成
- 參考書目