写在文章之前
一直想读这本书,但总是没有机会寻到,现偶然得到,细细读
来倍感激动,有如当年初读设计模式时的兴奋。独乐乐,不如同乐。
感尚无中文版本,所以小弟不才,翻译与大家同读。此地卧虎藏龙,
英文好我者不胜枚举,若有翻译不妥之处还请见谅。支持者UP一下
即可。
第一章: 重构,第一个例子
该如何开始写重构这本书呢?传统的方式是列出历史提纲、广
泛的原则等。当一些人在会议上开始这么做的时候,我则有些轻微的
睡意。面对讲演者 的陈词滥调我的头脑开始思考在较低层次的
处理上,直到她或他给出一个例子。 这将把我从昏昏欲睡中唤
醒,有一个例子能够使我知道下一步该干什么.用原理,太简单以至
于一般化,太难了很难指出该如何应用。而一个例子将会使问题变得
更清晰。
因此,我打算从一个重构的例子开始本书。在这个过程中,我将告诉
你如何重构以及让你对重构处理一个感性的认识。这样接下来能让我
提供一个通常的原理风格上的介绍。
然而,随着一个例子的引入,我将碰上一个一个大问题。如果我选择
一个大程序,描述她如何重构,这对于打多数的读者来说过于复杂以
至于不能完成工作。(我尝试后发觉即便是一个轻微复杂的例子也超
过100页)另一方面,如果我选择一个容易被理解的小程序,那会使
得重构看起来没有价值。
因此,我将使用经典著作中的方式通过引入一个真实世界中的程序来
描述这门技术。坦白的说,我展示给你的一段我打算使用的小程序对
她重构是没有什么价值的,但是如果我展示给你的是一个大系统中的
一段代码,那么重构不久将变得重要。所以我不得不要求你将它看作
是一个大系统中的一部分。
开始点
程序非常简单。他计算并打印一名客户在录像带出租店的收费情况。
程序输入客户借阅的电影和借阅时间。通过借阅的日期长度和借阅的
电影类型来计算收费。这儿有三种类型的影片:通常、儿童、新片。
另外通过计算费用,可以估租借热点,不在依赖于电影是否是新版。
下面几个类将代表不同的元素,类图如下所示:
图1.1 开始点得类图,仅最重要的特性被描述。使用UML符号。
我将轮流展示这些类的代码。
Movie
Movie是一个简单得类。
public class Movie{
public static final int SHILDRENS =2;
public static final int REGULAR =0;
public static final int NEW_RELEASEE =1;
private String _title ;
private int _priceCode ;
public Movie(String title,int priceCode){
_title = title ;
_priceCode = priceCode;
}
public int getPriceCode(){
return _priceCode;
}
public void setPriceCode(int arg){
_priceCode = arg;
}
public void getTitle(){
return _title;
}
}
Rental
Rental 代表一个客户借一部电影
class Rental {
private Movie _movie;
private int _daysRented;
public Rental(Movie movie,int daysRented){
_movie = movie;
_daysRented = daysRented;
}
public int getDaysRented(){
return _daysRented;
}
public Movie getMovie(){
return _movie;
}
}
Customer
Customer 代表录像店的一个客户,像其她的类一样她也有所数据和访问器。
class Customer{
private String _name;
private Vector _rentals = new Vector();
public Customer(String name){
_name = name;
}
public void addRental(Rental arg){
_rentals.addElement(arg);
}
public String getName(){
return _name;
}
}
Customer有一个方法实现 statement(收费情况)。图:1.2表明了这些方法
的交互。这个方法的主体是一个界面。
图1.2statement方法的交互图。
public String statement(){
double totalAmount =0;
int frequentRenterPoints =0;
Enumeration rentals = _rentals.elements();
String result ="Rental Record for "+getName()+"\n";
while(rentals.hasMoreElements()){
double thisAmount =0;
Rental each = (Rental)rentals.nextElement();
switch(each.getMovie().getPriceCode()){
case Movie.REGULAR:
thisAmount+=2;
if(each.getDaysRented()>2)
thisAmount+=(each.getDaysRented()-2)*1.5;
break;
case Movie.NEW_RELEASE:
thisAmount+=each.getDaysRented()*3;
break;
case Movie.CHILDRENS:
thisAmount+=1.5;
if(each.getDaysRented()>3)
thisAmount+=(each.getDaysRented()-3)*1.5;
break;
}
frequentRenterPoints++;
if((each.getMovie.getPriceCode()==Movie.NEW_RELEASE)&&
each.getDaysRented()>1)
frequentRenterPoints++;
//show figures for this rental
result+="\t"each.getMOVIE().getTitle()+"\t"+
String.valueOf(thisAmount)+"\n";
totalAmount += thisAmount;
}
//add footer lines
result+="Amount owed is "+ String .valueOf(totalAmount)+"\n";
result+="You earned "+String.valueOf(frequentRenterPoints)+
"frequent renter points";
return result;
}
关于这个程序
你对这个程序设计有什么印象?我对他的看法是他不是一个好的设计
并且也不是面向对象的。对于一个像这样的简单程序来说,这并不重要。
作为一个敏捷和不清晰的简单例子来说本身没有任何错误。但是如果这
是一个大系统的一部分,那么这个程序会有一些问题。statemetn方
法在Customer类中走得太远了。许多工作因该在其他类中来做。
即便程序能够工作。你是否愿意对丑陋的代码作美化呢?还是直到我们
改变系统。编译器是不会知道代码是否丑陋或清晰。当时当我改变一个
系统的时候,我们将卷入到系统中,我们会注意到这一点。一个糟糕的
设计很难被改变,因为很难指出到底那里需要被改变。如果难于指出要
改变什么,那么程序员将会犯错误或是引入新的BUG。
在这个例子中,我们将转变用户的想法。首先:用户想能够在WEB
上发布有关资费信息以满足客户的抱怨。考虑到这种改变,你要查看
你的源代码,看看是否有可能重用你的当前statement 方法的行为来
满足HTMLStatement。你唯一可行的方法是重新写具有相同特性的整
个方法,当然,这并不费力气,通过拷贝粘贴即可实现你的要求。
但是当收费规则改变呢?你不得不修改statement和HTMLStatement,以
确保他们在一致。问题是当以后再一次改变时,你不得不再一次拷贝很
粘贴代码。如果你期望你的程序不会再被改动,那么拷贝很粘贴是不错
的方法,但如果你的程序未来会被修改,那么拷贝很粘贴将带来混乱。
我们来介绍第二种改变。用户想法变电影分类方法,但是他们无法立即
决定下来,而是留待以后再决定。他们对此头脑中有一个想法。这些改
变将影响到出租方式、出租频率点数的计算。作为一个有经验的开发人
员,你应当更当上用户的计划,而
不是让你的计划停留在6个月内。
statement方法应该能够应付对于非类方法和收费规则的改变。但如果我
采取从statemet方法拷贝很粘贴到HTMLStatemtn我们他们的一致性。未
来随着规则的进一步复杂化,我们将很难指出到底那里被改变了,也很
难保证不引入新的错误。
你也许试图作经可能小的改变,毕竟她已经你能够工作。记一句古老的
工程谚语:“如果他自己破了,不要试图去修改她。“这个程序也许没
有破,但是他是有害的。当你发觉他不能适应用户的要求时,它会给你
带来困难。这就是为什么要重构。
提示
当你发现程序的结构不方便增加一个特性,而你不得不给程序增加一个
特性时,重构程序是它容易添加新特性,然后添加你的新
特性.
重构的一步。
待续。。。。。。。。。。。。。。。。。。。。。。。。。。