大话重构连载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

特别说明:希望网友们在转载本文时,应当注明作者或出处,以示对作者的尊重,谢谢!

posted @ 2014-08-26 11:00  充满诗意的联盟  阅读(1162)  评论(5编辑  收藏  举报