重构:改善既有代码的设计(2)
2011-09-12 23:03
417 查看
文章说明:
1.本文代码为了便于对照,先给出重构前代码,然后给出重构后的代码
2.原著作中部分UML图,本文未给出
1.用多态代替价格条件逻辑代码
经过前一阶段的重构,我们注意到switch语句:在Rental类中使用了Movie类的属性,这不是什么好主意,如果不得不使用,我们应该尽量在自己对象上使用自己的数据,而不应该过多的使用别人的数据,如:
这暗示我们应该先把getCharge()移动到Movie中:
为了能够让程序正常运行,我们需要将租期长度作为参数传递进去。然而,租期长度又来自于Rental类,这里你就可能产生疑问了。既然switch语句会影响两个类中的数据,我们为什么要把getCharge()从Rental(此方法在Rental中表示:将影片类型传递到Rental对象)类中移到Movie(将租期长度传递到Movie对象)类中呢?因为本系统可能发生的变化是影片类型的改变,这种变化是不稳定的。我希望引起的连锁反应是最小的,所以选择在Movie中计算费用。相应的,应该改变Rental类:
移动了getCharge()方法后,我以同样的手法处理点数(积分)的计算,以保证把将会因影片类型改变而改变的代码都放入Movie类中。Rental类就由:
变成:
相应的Movie类中新增加一方法:
2.终于说到继承了
我们有不同种类型的影片,它们以不同的类型回答相同的问题(影片类型的不同,都是为了计算出租赁的费用)。所以,我们想到了可以建立3个子类,每个子类都有自己的计费方式,它们计算各自的费用,它们的关系如下(以继承机制表现不同影片):
这样,我们就可以用多态取代switch语句了。但是遗憾的是,我们不能这么干,因为一部影片可以在自己的生命周期内修改自己的类型,一个对象却不能在生命周期类修改自己所属的类。但是,我们仍有解决的办法,那就是运用State模式(或译:状态模式),运用它以后,我们的类关系应该如下(运动State模式表现不同的影片):
这里增加了一个中间层,就可以在Price对象进行继承动作了,我们可以按照我们的需求随时改变Price(价格)。
如果你很熟悉设计模式,你可能会问:这是一个State还是一个strategy?答案取决于Price类究竟代表计费方式还是代表影片的某个状态。对于模式的选择反映出你对结构的想法,这里把它视为影片的某种状态。如果未来你觉得strategy能更好的说明你的意图,你可以再改造它以形成strategy。
接下来,我将运用3个重构准则。首先运用了Replace Type Code with State/strategy,将与影片类型相互依赖的行为(Type Code bahavior)移动到State模式内。然后运用Move Method将switch语句移到了Price类中,最后运用Replace Conditional with Polymorphism去掉switch语句。
首先我使用Replace Type Code with State/strategy。第一步将与类型相依赖的属性使用Self Encapsulate Field以确保任何时候都可以通过get和set方法获得这些属性。这样做是因为其它类中的很多代码都已经使用了这些属性,大多数方法也通过get方法来获取这些属性。当然,构造方法仍然可以直接使用属性值。
构造方法中我们可以用setter代替,如:
现在我加入新的类,在Price 类中提供抽象方法getPriceCode(与类型相依赖的行为),子类中来实现这个抽象方法:
先在我要修改Movie的访问方法(get和set方法),下面是重构前的样子:
这意味着,我必须在Movie类中保存一个Price对象而不再是一个priceCode变量,此外我还要修改访问方法:
第二步,我将对getCharge()运用Move Method,重构之前如下:
重构之后:
第3步,我将运用Replace Conditional with Polymorphism,将switch的每一个分支用于一个子类的覆写方法,重构后如下:
最后,在对象点数(积分)的计算采用同样的方法重构,这里不再累赘叙述。这里需要注意的是普通片和儿童片的积分点数是1,新片的积分点数是2,重构的方法看下面的UML图就容易理解了:
引入State模式花费了我们不少功夫,这样做是否值得呢?如果我要修改与任何与价格有关的行为,增加一个新的价格标准,或者其它有关价格的行为,我都将很容易的对系统进行修改,而这个程序的其它部分并不知道我运用了State模式。由于目前程序中的行为太少,所以我们修改起来很容易,当在一个大的系统中,比如与价格相关的行为有十多个,修改的难度与这个相比将会有很大区别。
3.结语
这是一个简单的实例,我希望它能够让你对重构有一点感觉。实例中我使用了几个重构准则:Extract Method,Move Method和Replace Conditional with Polymorpbism。所有这些重构行为的目的都是为了是责任分配更合理,代码维护更容易。它将与结构化的编程方式有很大区别,尽管很多人习惯后者。不过只要你一习惯重构后的风格,你就很难在回到过去了,因为结构化的编程风格已经不能满足你的需求了。
这个实例给你上的最重要一课:重构的节奏,测试,小修改,测试,小修改,测试,小修改…这是这样的节奏让重构既快速又安全。如果你能跟上这个节奏,你现在应该对重构有一个基本了解了,后面我们将了解一点背景,原理和理论,当然只是一点点。
1.本文代码为了便于对照,先给出重构前代码,然后给出重构后的代码
2.原著作中部分UML图,本文未给出
1.用多态代替价格条件逻辑代码
经过前一阶段的重构,我们注意到switch语句:在Rental类中使用了Movie类的属性,这不是什么好主意,如果不得不使用,我们应该尽量在自己对象上使用自己的数据,而不应该过多的使用别人的数据,如:
public class Rental... public double getCharge() { double result = 0; switch(getMovie().getPriceCode()) { //各种影片的价格不同 case Movie.REGULAR: result += 2; if(getDaysRented()>2) result += (getDaysRented()-2)*1.5; break; case Movie.NEW_RELEASE: result += getDaysRented()*3; break; case Movie.CHILDRENS: result += 1.5; if(getDaysRented()>3) result += (getDaysRented()-3)*1.5; break; } return result; }
这暗示我们应该先把getCharge()移动到Movie中:
public class Movie ... public double getCharge(int daysRented) { double result = 0; switch(getPriceCode()) { //各种影片的价格不同 case Movie.REGULAR: result += 2; if(daysRented>2) result += (daysRented-2)*1.5; break; case Movie.NEW_RELEASE: result += daysRented*3; break; case Movie.CHILDRENS: result += 1.5; if(daysRented>3) result += (daysRented-3)*1.5; break; } return result; } }
为了能够让程序正常运行,我们需要将租期长度作为参数传递进去。然而,租期长度又来自于Rental类,这里你就可能产生疑问了。既然switch语句会影响两个类中的数据,我们为什么要把getCharge()从Rental(此方法在Rental中表示:将影片类型传递到Rental对象)类中移到Movie(将租期长度传递到Movie对象)类中呢?因为本系统可能发生的变化是影片类型的改变,这种变化是不稳定的。我希望引起的连锁反应是最小的,所以选择在Movie中计算费用。相应的,应该改变Rental类:
public class Rental ... public double getCharge() { return movie.getCharge(daysRented); }
移动了getCharge()方法后,我以同样的手法处理点数(积分)的计算,以保证把将会因影片类型改变而改变的代码都放入Movie类中。Rental类就由:
public class Rental ... public int getFrequentRenterPoints() { if((getMovie().getPriceCode()==Movie.NEW_RELEASE)&& getDaysRented()>1) return 2; else return 1; } }
变成:
public class Rental ... public int getFrequentRenterPoints() { return movie.getFrequentRenterPoints(daysRented); } }
相应的Movie类中新增加一方法:
public class Movie... public int getFrequentRenterPoints(int daysRented) { if(getPriceCode()==Movie.NEW_RELEASE && daysRented>1) return 2; else return 1; } }
2.终于说到继承了
我们有不同种类型的影片,它们以不同的类型回答相同的问题(影片类型的不同,都是为了计算出租赁的费用)。所以,我们想到了可以建立3个子类,每个子类都有自己的计费方式,它们计算各自的费用,它们的关系如下(以继承机制表现不同影片):
这样,我们就可以用多态取代switch语句了。但是遗憾的是,我们不能这么干,因为一部影片可以在自己的生命周期内修改自己的类型,一个对象却不能在生命周期类修改自己所属的类。但是,我们仍有解决的办法,那就是运用State模式(或译:状态模式),运用它以后,我们的类关系应该如下(运动State模式表现不同的影片):
这里增加了一个中间层,就可以在Price对象进行继承动作了,我们可以按照我们的需求随时改变Price(价格)。
如果你很熟悉设计模式,你可能会问:这是一个State还是一个strategy?答案取决于Price类究竟代表计费方式还是代表影片的某个状态。对于模式的选择反映出你对结构的想法,这里把它视为影片的某种状态。如果未来你觉得strategy能更好的说明你的意图,你可以再改造它以形成strategy。
接下来,我将运用3个重构准则。首先运用了Replace Type Code with State/strategy,将与影片类型相互依赖的行为(Type Code bahavior)移动到State模式内。然后运用Move Method将switch语句移到了Price类中,最后运用Replace Conditional with Polymorphism去掉switch语句。
首先我使用Replace Type Code with State/strategy。第一步将与类型相依赖的属性使用Self Encapsulate Field以确保任何时候都可以通过get和set方法获得这些属性。这样做是因为其它类中的很多代码都已经使用了这些属性,大多数方法也通过get方法来获取这些属性。当然,构造方法仍然可以直接使用属性值。
public class Movie ... private String title; //片名 private int priceCode; //价格代号 //getter and setter }
构造方法中我们可以用setter代替,如:
public class Movie ... private String title; //片名 private int priceCode; //价格代号 //getter and setter public Movie(String title, int priceCode) { this.title = title; setPriceCode(priceCode); } }
现在我加入新的类,在Price 类中提供抽象方法getPriceCode(与类型相依赖的行为),子类中来实现这个抽象方法:
public abstract class Price { abstract int getPriceCode(); } public class ChildrensPrice extends Price { @Override int getPriceCode() { return Movie.CHILDRENS; } } public class NewReleasePrice extends Price { @Override int getPriceCode() { return Movie.NEW_RELEASE; } } public class RegularPrice extends Price { @Override int getPriceCode() { return Movie.REGULAR; } }
先在我要修改Movie的访问方法(get和set方法),下面是重构前的样子:
private int priceCode; //价格代号 public int getPriceCode() { return priceCode; } public void setPriceCode(int priceCode) { this.priceCode = priceCode; }
这意味着,我必须在Movie类中保存一个Price对象而不再是一个priceCode变量,此外我还要修改访问方法:
public class Movie ... private Price price; public int getPriceCode() { return priceCode; } public void setPriceCode(int arg) { switch(arg) { //各种影片的价格不同 case REGULAR: price = new RegularPrice(); break; case NEW_RELEASE: price = new NewReleasePrice(); break; case Movie.CHILDRENS: price = new ChildrensPrice(); break; default: throw new IllegalArgumentException("Incorrect Price Code"); } }
第二步,我将对getCharge()运用Move Method,重构之前如下:
public class Movie ... public double getCharge(int daysRented) { double result = 0; switch(getPriceCode()) { //各种影片的价格不同 case Movie.REGULAR: result += 2; if(daysRented>2) result += (daysRented-2)*1.5; break; case Movie.NEW_RELEASE: result += daysRented*3; break; case Movie.CHILDRENS: result += 1.5; if(daysRented>3) result += (daysRented-3)*1.5; break; } return result; }
重构之后:
public class Movie ... public double getCharge(int daysRented) { return price.getCharge(daysRented); } public abstract class Price ... public double getCharge(int daysRented) { double result = 0; switch(getPriceCode()) { //各种影片的价格不同 case Movie.REGULAR: result += 2; if(daysRented>2) result += (daysRented-2)*1.5; break; case Movie.NEW_RELEASE: result += daysRented*3; break; case Movie.CHILDRENS: result += 1.5; if(daysRented>3) result += (daysRented-3)*1.5; break; } return result; } }
第3步,我将运用Replace Conditional with Polymorphism,将switch的每一个分支用于一个子类的覆写方法,重构后如下:
public abstract class Price { abstract double getCharge(int daysRented); } public class RegularPrice extends Price { public double getCharge(int daysRented) { double result = 2; if(daysRented>2) result += (daysRented-2)*1.5; return result; } } public class NewReleasePrice extends Price { @Override public double getCharge(int daysRented) { return daysRented*3; } } public class ChildrensPrice extends Price { @Override public double getCharge(int daysRented) { double result = 1.5; if(daysRented>3) result += (daysRented-3)*1.5; return result; } }
最后,在对象点数(积分)的计算采用同样的方法重构,这里不再累赘叙述。这里需要注意的是普通片和儿童片的积分点数是1,新片的积分点数是2,重构的方法看下面的UML图就容易理解了:
引入State模式花费了我们不少功夫,这样做是否值得呢?如果我要修改与任何与价格有关的行为,增加一个新的价格标准,或者其它有关价格的行为,我都将很容易的对系统进行修改,而这个程序的其它部分并不知道我运用了State模式。由于目前程序中的行为太少,所以我们修改起来很容易,当在一个大的系统中,比如与价格相关的行为有十多个,修改的难度与这个相比将会有很大区别。
3.结语
这是一个简单的实例,我希望它能够让你对重构有一点感觉。实例中我使用了几个重构准则:Extract Method,Move Method和Replace Conditional with Polymorpbism。所有这些重构行为的目的都是为了是责任分配更合理,代码维护更容易。它将与结构化的编程方式有很大区别,尽管很多人习惯后者。不过只要你一习惯重构后的风格,你就很难在回到过去了,因为结构化的编程风格已经不能满足你的需求了。
这个实例给你上的最重要一课:重构的节奏,测试,小修改,测试,小修改,测试,小修改…这是这样的节奏让重构既快速又安全。如果你能跟上这个节奏,你现在应该对重构有一个基本了解了,后面我们将了解一点背景,原理和理论,当然只是一点点。
相关文章推荐
- 『重构--改善既有代码的设计』读书笔记----Inline Temp
- 重构:改善既有代码的设计 精彩书评二
- 2016书单总结--重构改善既有代码的设计--重新组织函数
- <<重构改善既有的代码设计>>第一章节
- 重构-改善既有代码设计 读书笔记
- 编程经典书籍推荐:重构 改善既有代码的设计
- 《重构--改善既有代码的设计》--在对象之间搬移特性(7)
- [201004][重构@改善既有代码的设计][熊节][译]
- 重构-改善既有代码的设计:简化条件表达式(七)
- 『重构--改善既有代码的设计』读书笔记----Hide Delegate
- 重构-改善既有代码的设计读书笔记(三)
- 重构-改善既有代码的设计:处理概括关系 (九)
- 重构 改善既有代码的设计读后总结
- 读《重构 改善既有代码的设计》有感
- 重构-改善既有代码的设计(1)
- 重构—改善既有代码的设计
- 重构-改善既有代码的设计(2.4)
- 重构改善既有代码设计--重构手法09:Substitute Algorithm (替换算法)
- 重构--改善既有代码的设计
- 《重构-改善既有代码的设计》读后总结