以下是一個創建資源的簡單構造塊:
public static ResourceBuilder newResource (String userName) {
ResourceBuilder rb = new ResourceBuilder();
rb.userName = userName + UnitTestThreadContext.getUniqueSuffix();
return rb; }
public ResourceBuilder assignRole(String roleName) {
this.roleName = roleName + UnitTestThreadContext.getUniqueSuffix();
return this;
}
public Resource create() {
ResourceDAO resourceDAO = new ResourceDAO(UnitTestThreadContext.getSession());
Resource rs;
if (StringUtils.isNotBlank(userName)) {
rs = resourceDAO.createResource(this.userName);
} else {
throw new RuntimeException("must have a user name to create a resource");
}
if (StringUtils.isNotBlank(roleName)) {
Role role = RoleBuilder.newRole(roleName).create();
rs.addRole(role);
}
return rs;
}
public static void delete(Resource rs, boolean cascadeToRole) {
Session session = UnitTestThreadContext.getSession();
ResourceDAO resourceDAO = new ResourceDAO(session);
resourceDAO.delete(rs);
if (cascadeToRole) {
RoleDAO roleDAO = new RoleDAO(session);
List roles = rs.getRoles();
for (Object role : roles) {
roleDAO.delete((Role)role);
}
}
}
ResourceBuilder是創建者模式與工廠模式的一個實現,你可以以方法鏈接的形式使用它:
ResourceBuilder.newResource(“Tom”).assignRole(“Developer”).create();
其中包含了一個打掃戰場的方法delete(),在這次重構練習的早期,我并沒有非常頻繁地調用delete()方法,因為我經常啟動整個系統并添加一些測試數據以檢查餅圖是否正確顯示。
UnitTestThreadContext類非常有用,它保存了某個特定于線程的Hibernate Session對象,并且為你打算創建的實體提供了唯一字符串作為名稱前綴,以此保證實體的唯一性。
public class UnitTestThreadContext {
private static ThreadLocal threadSession=new ThreadLocal();
private static ThreadLocal threadUniqueId=new ThreadLocal();
private final static SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy/MM/
dd HH_mm_ss_S");
public static Session getSession(){>
Session session = threadSession.get();
if (session==null) {
throw new RuntimeException("Hibernate Session not set!");
}
return session;
}
public static void setSession(Session session) {
threadSession.set(session);
}
public static String getUniqueSuffix() {
String uniqueId = threadUniqueId.get();
if (uniqueId==null){
uniqueId = "-"+dateFormat.format(new Date());
threadUniqueId.set(uniqueId);
}
return uniqueId;
}
…
}
完成重構
現在我終于可以啟動一個最小化的可運行架構,并執行這個簡單的測試用例了:
protected void setUp() throws Exception {
TestServerMain.run(); //setup a minimum running infrastructure
}
public void testResourceBreakdown(){
Resource resource=createResource(); //use ResourceBuilder to build unique resources
List requests=createRequests(); //use RequestBuilder to build unique requests
assignRequestToResource(resource, requests);
List tickets=createTickets(); //use TicketBuilder to build unique tickets
assignTicketToResource(resource, tickets);
Map result=new ResourceBreakdownService().search(resource);
verifyResult(result);
}
protected void tearDown() throws Exception {
// use TicketBuilder.delete() to delete tickets
// use RequestBuilder.delete() to delete requests
// use ResourceBuilder.delete() to delete resources
接下來我又編寫了多個更復雜的測試用例,并且一邊重構產品代碼一邊編寫測試代碼。
有了這些測試用例,我就可以將ResourceBreakdownService那個上帝類一點點進行分解。具體的細節就不必多啰嗦了,市面上已經有許多優秀書籍指導你如何安全地進行重構。為了本文的完整性,以下是重構后的結構圖:
那個恐怖的“數組套Map再套數組再套Map……”數據結構現在已經組織為新的ResourceLoadBucket類了,它的實現用到了組合模式。它首先包含了某個特別級別的預計完成時間和實際完成時間,下一個級別的完成時間將通過aggregate()方法聚合之后得到。最終的代碼干凈了許多,而且性能也更好。它也暴露了隱藏在原始代碼的復雜性中的一些缺陷。當然,我也同時改進了我的測試用例。
原文轉自:http://www.infoq.com/cn/articles/refactoring-legacy-applications