大话重构连载11:小步快跑是这样玩的
说了那么多,相信你对小步快跑的概念有了一个初步的印象,但理解还不是很深。让我们来看一看一个实际工作中的例子,来亲身感受一下什么是大布局,什么是大设计,什么是小设计。
还是回到前面那个Hello World的例子,起初的需求总是简单而清晰的。当用户登录一个网站时,网站往往需要给用户打一个招呼:“hi, XXX! ”。同时,如果此时是上午则显示“Good morning! ”,如果是下午则显示“Good afternoon! ”,除此显示“Good night! ”。对于这样一个需求我们在一个HelloWorld类中写了十来行代码:
1 /** 2 * The Refactoring's hello-world program 3 * @author fangang 4 */ 5 public class HelloWorld { 6 /** 7 * Say hello to everyone 8 * @param now 9 * @param user 10 * @return the words what to say 11 */ 12 public String sayHello(Date now, String user){ 13 //Get current hour of day 14 Calendar calendar = Calendar.getInstance(); 15 calendar.setTime(now); 16 int hour = calendar.get(Calendar.HOUR_OF_DAY); 17 18 //Get the right words to say hello 19 String words = null; 20 if(hour>=6 && hour<12){ 21 words = "Good morning!"; 22 }else if(hour>=12 && hour<19){ 23 words = "Good afternoon!"; 24 }else{ 25 words = "Good night!"; 26 } 27 words = "Hi, "+user+". "+words; 28 return words; 29 } 30 }
如果需求没有变更,一切都是美好的。但事情总是这样,当软件第一次提交,变更就开始了。系统总是不能直接获得用户名称,而是先获得他的userId,然后通过userId从数据库中获得用户名。后面的问候可能需要更加精细,如中午问候“Good noon! ”、傍晚问候“Good evening! ”、午夜问候“Good midnight! ”。除此之外,用户希望在一些特殊的节日,如新年问候“Happy new year! ”、情人节问候“Happy valentine’s day! ”、三八妇女节问候“Happy women’s day! ”,等等。除了已经列出的节日,他们还希望临时添加一些特殊的日子,因此问候语需要形成一个库,并支持动态添加。不仅如此,这个问候库应当支持多语言,如选择英语则显示“Good morning! ”,而选择中文则显示“上午好!”……总之,各种不同的需求被源源不断地被用户提出来,因此我们的设计师开始头脑发热、充血、开始思维混乱。是的,如果你期望你自己能一步到位搞定所有这些需求,你必然会感到千头万绪、顾此失彼,进而做出错误的设计。但如果你学会了“小步快跑”的开发模式,一切就变得没有那么复杂了。
首先,我们观察原程序,发现它包含三个相对独立的功能代码段,因此我们采用重构中的“抽取方法”,将它们分别抽取到三个函数getHour(), getFirstGreeting(), getSecondGreeting()中,并让原函数对其引用:
1 /** 2 * The Refactoring's hello-world program 3 * @author fangang 4 */ 5 public class HelloWorld { 6 /** 7 * Say hello to everyone 8 * @param now 9 * @param user 10 * @return the words what to say 11 */ 12 public String sayHello(Date now, String user){ 13 int hour = getHour(now); 14 return getFirstGreeting(user)+getSecondGreeting(hour); 15 } 16 17 /** 18 * Get current hour of day. 19 * @param now 20 * @return current hour of day 21 */ 22 private int getHour(Date now){ 23 Calendar calendar = Calendar.getInstance(); 24 calendar.setTime(now); 25 return calendar.get(Calendar.HOUR_OF_DAY); 26 } 27 28 /** 29 * Get the first greeting. 30 * @param user 31 * @return the first greeting 32 */ 33 private String getFirstGreeting(String user){ 34 return "Hi, "+user+". "; 35 } 36 37 /** 38 * Get the second greeting. 39 * @param hour 40 * @return the second greeting 41 */ 42 private String getSecondGreeting(int hour){ 43 if(hour>=6 && hour<12){ 44 return "Good morning!"; 45 }else if(hour>=12 && hour<19){ 46 return "Good afternoon!"; 47 }else{ 48 return "Good night!"; 49 } 50 } 51 }
这次重构虽然使程序结构发生了较大变化,但其中真正执行的代码却没有变化,还是那些代码。随后,我们核对需求发现,用户需求分成了两个不同的分支:对用户问候语的变更,和关于时间的问候语变更。为此,我们再次对HelloWorld的程序进行了分裂,运用重构中的“抽取类”,将对用户问候的程序分裂到GreetingToUser类中,将关于时间的问候程序分裂到GreetingAboutTime类中:
1 /** 2 * The Refactoring's hello-world program 3 * @author fangang 4 */ 5 public class HelloWorld { 6 /** 7 * Say hello to everyone 8 * @param now 9 * @param user 10 * @return the words what to say 11 */ 12 public String sayHello(Date now, String user){ 13 GreetingToUser greetingToUser = new GreetingToUser(user); 14 GreetingAboutTime greetingAboutTime = new GreetingAboutTime(now); 15 return greetingToUser.getGreeting() + greetingAboutTime.getGreeting(); 16 } 17 } 18 19 /** 20 * The greeting to user 21 * @author fangang 22 */ 23 public class GreetingToUser { 24 private String user; 25 /** 26 * The constructor with user 27 * @param user 28 */ 29 public GreetingToUser(String user){ 30 this.user = user; 31 } 32 /** 33 * @return greeting to user 34 */ 35 public String getGreeting(){ 36 return "Hi, "+user+". "; 37 } 38 } 39 40 /** 41 * The greeting about time. 42 * @author fangang 43 */ 44 public class GreetingAboutTime { 45 private Date date; 46 public GreetingAboutTime(Date date){ 47 this.date = date; 48 } 49 /** 50 * @param date 51 * @return the hour of day 52 */ 53 private int getHour(Date date){ 54 Calendar calendar = Calendar.getInstance(); 55 calendar.setTime(date); 56 return calendar.get(Calendar.HOUR_OF_DAY); 57 } 58 /** 59 * @return the greeting about time 60 */ 61 public String getGreeting(){ 62 int hour = getHour(date); 63 if(hour>=6 && hour<12){ 64 return "Good morning!"; 65 }else if(hour>=12 && hour<19){ 66 return "Good afternoon!"; 67 }else{ 68 return "Good night!"; 69 } 70 } 71 }
系统重构到这一步,我们来看看用户关于时间问候语部分的变更需求:问候需要更加精细,如中午问候“Good noon! ”、傍晚问候“Good evening! ”、午夜问候“Good midnight! ”。除此之外,用户希望在一些特殊的节日,如新年问候“Happy new year! ”、情人节问候“Happy valentine’s day! ”、三八妇女节问候“Happy women’s day! ”,等等。此时我们发现,我们对时间问候语的变更不再需要修改HelloWorld或其它什么类,而是仅仅专注于修改GreetingAboutTime就可以了,这就是因重构带来的改善。
同时,我们发现,过去只需getHour()就足够,而现在却需要getMonth()与getDay()。随着程序复杂度的提升,我们适时进行了一次重构,将与时间相关的程序抽取到一个新类DateUtil中,就可以顺利地改写原有的时间问候语程序:
1 /** 2 * The utility of time 3 * @author fangang 4 */ 5 public class DateUtil { 6 private Calendar calendar; 7 /** 8 * @param date 9 */ 10 public DateUtil(Date date){ 11 calendar = Calendar.getInstance(); 12 calendar.setTime(date); 13 } 14 /** 15 * @return the hour of day 16 */ 17 public int getHour(){ 18 return calendar.get(Calendar.HOUR_OF_DAY); 19 } 20 /** 21 * @return the month of date 22 */ 23 public int getMonth(){ 24 return calendar.get(Calendar.MONTH)+1; 25 } 26 /** 27 * @return the day of month 28 */ 29 public int getDay(){ 30 return calendar.get(Calendar.DAY_OF_MONTH); 31 } 32 } 33 34 /** 35 * The greeting about time. 36 * @author fangang 37 */ 38 public class GreetingAboutTime { 39 private Date date; 40 public GreetingAboutTime(Date date){ 41 this.date = date; 42 } 43 /** 44 * @return the greeting about time 45 */ 46 public String getGreeting(){ 47 DateUtil dateUtil = new DateUtil(date); 48 int month = dateUtil.getMonth(); 49 int day = dateUtil.getDay(); 50 int hour = dateUtil.getHour(); 51 52 if(month==1 && day==1) return "Happy new year! "; 53 if(month==1 && day==14) return "Happy valentine's day! "; 54 if(month==3 && day==8) return "Happy women's day! "; 55 if(month==5 && day==1) return "Happy Labor day! "; 56 ...... 57 58 if(hour>=6 && hour<12) return "Good morning!"; 59 if(hour==12) return "Good noon! "; 60 if(hour>=12 && hour<19) return "Good afternoon! "; 61 if(hour>=19 && hour<22) return "Good evening! "; 62 return "Good night! "; 63 } 64 }
最后,我们建立user表存放用户信息,创建UserDao接口及其实现类,为GreetingToUser提供用户信息访问的服务;我们用greetingRule表存放各种问候语,创建GreetingRuleDao接口及其实现类,为GreetingAboutTime提供一个可扩展的、支持多语言的问候语库(如图3.1所示)。所有这一切都是在现有基础上,通过小步快跑的方式一步一步演变的。
图3.1 HelloWorld的设计图
小步快跑是一种逐步进化式的程序优化过程,它是重构思想的重要核心。后面我们还会用更多实际工作中的示例,让你真实体会到小步快跑的开发过程。
大话重构连载首页:http://www.cnblogs.com/mooodo/p/talkAboutRefactoringHome.html
特别说明:希望网友们在转载本文时,应当注明作者或出处,以示对作者的尊重,谢谢!