在我的編程生涯中,碰到類似Copy-Paste的情況簡直不勝枚舉。在一次項(xiàng)目中,我們對開源項(xiàng)目Jasper Report進(jìn)行了擴(kuò)展。我們加入了對新報表類型(CJT_REPORT)的支持。在ReportParameterAction類中,我們需要對報表對象ReportUnit判斷報表類型。于是,我在ReportParameterAction類中定義了如下的私有方法:
private void setReportUnitTypeByFileResource(ReportUnit reportUnit) {
final String JS_FILE_TYPE = "jrxml";
ResourceReference reference = reportUnit.getMainReport();
if (reference.isLocal()) {
FileResource resource = (FileResource)reference.getLocalResource();
String fileType = resource.getFileType();
if (fileType.toLowerCase().equals(JS_FILE_TYPE)){
reportUnit.setReportType(ReportUnit.JS_REPORT);
} else {
reportUnit.setReportType(ReportUnit.CJT_REPORT);
}
}
}
該方法根據(jù)FileResource中的文件類型獲得對應(yīng)的報表類型。這一方法在ReportParameterAction類的createWrappers()方法中被調(diào)用。
protected Event createWrappers(RequestContext context) {
ReportUnit reportUnit = loadReportUnit(context);
setReportUnitTypeByFileResource(reportUnit);
InitialRequestContext(context, reportUnit);
int reportType = reportUnit.getReportType();
if (reportType == ReportUnit.CJT_REPORT) {//……}
//……
}
后來,我發(fā)現(xiàn)在EngineServiceImpl類中同樣需要判斷報表的類型。然而,setReportUnitTypeByFileResource()方法卻被定義為ReportParameterAction的私有方法,無法被EngineServiceImpl對象調(diào)用。最簡單的做法是采用復(fù)制的方式,將這段代碼復(fù)制到EngineServiceImpl中。好了,如果此時我們不能忍住copy-paste的簡便所帶來的誘惑,或許就會陷入重復(fù)的泥沼了。Copy的動作絕對應(yīng)該鳴起刺耳的警聲。我們需要對這一做法保持足夠的警惕。
通過分析setReportUnitTypeByFileResource()方法,我發(fā)現(xiàn)該方法需要操作的對象均與ReportUnit有關(guān),而本身該方法的職責(zé)就是要獲得ReportUnit的類型,并對其字段reportType進(jìn)行設(shè)置,因此,完全可以將它搬移到ReportUnit中(Move Method重構(gòu),不是嗎?)。由于ReportUnit已經(jīng)增加了對reportType字段的get和set訪問方法,我發(fā)現(xiàn)它們與我要重構(gòu)的方法實(shí)有異曲同工之處,因而決定將其搬移到getReportType()方法中:
public int getReportType() {
if (hasReportTypeBeenSet()) {
return reportType;
}
return getReportTypeByFileResource();
}
private int getReportTypeByFileResource() {
final String CJT_FILE_TYPE = "cjtxml";
int reportType = ReportUnit.JS_REPORT;
ResourceReference reference = this.getMainReport();
if (reference != null) {
if (reference.isLocal()) {
FileResource resource = (FileResource) reference
.getLocalResource();
if (resource != null) {
String fileType = resource.getFileType();
if (fileType.toLowerCase().equals(CJT_FILE_TYPE)) {
reportType = ReportUnit.CJT_REPORT;
}
}
}
}
return reportType;
}
private boolean hasReportTypeBeenSet(){
if (reportType != ReportUnit.JS_REPORT
&& reportType != ReportUnit.CJT_REPORT) {
return false;
} else {
return true;
}
}
注意,在以上過程中,除了使用Move Method之外,我還運(yùn)用了Rename Method以及Extract Method等重構(gòu)手法。
現(xiàn)在,對報表類型的獲取與調(diào)用就變得簡單了,在ReportParameterAction與EngineServiceImpl類中,也規(guī)避了重復(fù)代碼的產(chǎn)生。例如,在createWrappers()方法中的調(diào)用被更改為:
protected Event createWrappers(RequestContext context) {
ReportUnit reportUnit = loadReportUnit(context);
setReportUnitTypeByFileResource(reportUnit);
InitialRequestContext(context, reportUnit);
int reportType = reportUnit.getReportType();
if (reportType == ReportUnit.CJT_REPORT) {//……}
//……
}