重構之美之一利用多態重構為帶參方法
我在閱讀遺留代碼時,經常發現存在這樣一種情形。在一個類中存在兩個方法,它們做了相似的工作,區別僅在于方法內部某些對象的類型。例如:
private void fillHeader() {
Header header = createHeader();
for (String title:header.getTitles()) {
fillCell(title);
}
}
Private void fillBody() {
CellGroup cellGroup = createCellGroup();
for (Cell cell:cellGroup.getCells()) {
fillCell(cell.getText());
}
}
}
方法fillHeader()和fillBody()的目的都是從對象中獲得字符串數組,然后將其填充到單元格中。區別在于,獲得字符串數組的對象并不相同。前者為Header對象,后者為CellGroup對象。我們可以為其提供一個抽象的接口,以實現代碼的有效重用:
public String[] getTextArea();
}
然后,讓Header和CellGroup類均實現該接口。由于CellGroup并沒有直接定義返回字符串數組的方法,而是通過返回的Cell對象獲得text值,因此,需要將這部分實現封裝到getTextArea()方法中:
@Override
public String[] getTextArea() {
//這里的實現即為原有的getTitles()實現
//可以保留原有的方法,并在本方法中指向該方法
//也可以利用Rename Method手法,直接更名該方法
}
}
public class CellGroup implements TextDataSource {
@Override
public String[] getTextArea() {
List<String> textArea = new ArrayList<String>();
for (Cell cell:this.getCells()) {
textArea.add(cell.getText());
}
return textArea.toArray();
}
}
現在,就可以重構原有的WorkSheet類了。
private void fillSheet(TextDataSource dataSource) {
for (String text:dataSource.getTextArea()) {
fillCell(text);
}
}
}
具體需要填充什么內容,可以在調用fillSheet()方法時,根據傳入的參數對象來決定。經過重構之后,WorkSheet類中的重復代碼得到了移除,且具有了更好的擴展性。
這一重構手法與Parameterize Method要解決的壞味道相似,同樣對相似方法提取了共同的參數,但實現的本質完全不同。它利用了多態的原理,通過對抽象方法體中的相似對象,抹去了不同類型對象之間的差異性,使得方法體中的相似結構能夠被抽取出來。
我將這一重構手法命名為Parameterize Method by Polymorphism。讓我仿照Martin Fowler的風格,給出這一重構方式的作法(Mechanics):
1)新建一個接口,并使原有方法中的差異對象實現該接口。
2)如果原有對象的方法與該接口定義的方法簽名不同,則運用Rename Method。
3)編譯。
4)新建一個參數為新接口類型的方法,使它可以替換先前所有的重復方法。
5)編譯。
6)將對舊方法的調用替換為對新函數的調用。
7)編譯,測試。
8)對所有舊方法重復上述步驟,每次替換后,修改并測試
如果在調用新方法時,發現創建參數實參對象是一件麻煩事,可以考慮在原有類中引入一個創建新接口對象的工廠方法,從而對復雜的創建邏輯進行封裝。