重构遗留程序的一次案例学习(4)

发表于:2013-12-20来源:InfoQ作者:Chen Ping点击数: 标签:重构
以下是一个创建资源的简单构造块: public static ResourceBuilder newResource (String userName) { ResourceBuilder rb = new ResourceBuilder(); rb.userName = userName + UnitTestThreadContex

  以下是一个创建资源的简单构造块:

  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