上一篇文章:java 代码重构-第一章(运用多态(Polymorphism)取代与价格相关的条件逻辑)
下一篇文章:java 代码重构-第一章(使用策略模式,把恶心的switch代码去掉...) 一
终于……我们来到继承(Inheritance)
我们有数种影片类型,它们以不同的方式回答相同的问题。这听起来很像subclasses 的工作。我们可以建立Movie 的三个subclasses ,每个都有自己的计费法(图1.14)。
这么一来我就可以运用多态(polymorphism)来取代switch 语句了。很遗憾的是这里有个小问题,不能这么干。一部影片可以在生命周期内修改自己的分类,一个对象却不能在生命周期内修改自己所属的class。不过还是有一个解决方法:State pattern(模式)[Gang of Four]。运用它之后,我们的classes 看起来像图1.15。
加入这一层间接性,我们就可以在Price 对象内进行subclassing 动作(译注:一如图1.15),于是便可在任何必要时刻修改价格。
如果你很熟悉Gang of Four 所列的各种模式(patterns),你可能会问:『这是一个State 还是一个Strategy?』答案取决于Price class 究竟代表计费方式(此时我喜欢把它叫做Pricer 或PricingStrategy),或是代表影片的某个状态(state,例如「Star Trek X 是一部新片」)。在这个阶段,对于模式(和其名称)的选择反映出你对结构的想法。此刻我把它视为影片的某种状态(state)。如果未来我觉得Strategy 能更好地说明我的意图,我会再重构它,修改名字,以形成Strategy 。
为了引入State 模式,我使用三个重构准则。首先运用Replace Type Code with State/Strategy,将「与型别相依的行为」(type code behavior )搬移至State 模式内。然后运用Move Method 将switch 语句移到Price class 里头。最后运用Replace Conditional with Polymorphism去掉switch 语句。
首先我要使用Replace Type Code with State/Strategy。第一步骤是针对「与 型别相依的行为」使用Self Encapsulate Field,确保任何时候都通过getting 和setting 两个函数来运用这些行为。由于多数代码来自其他classes,所以多数函数都己经使用getting 函数。但构造函数(constructor )仍然直接访问价格代号(译注:程序中的priceCode):
public Movie(String title, int priceCode) {
this.title = title;
this.priceCode = priceCode;
}
然后编译并测试,确保没有破坏任何东西。
输出结果:
Rental Record for oyhk
少林足球 6.0
大话西游 1.5
Amount owed is 7.5
You earned 3 frequent renter points
------------------------------------------------
<H1>Rentals for <EM>oyhk</EM></ H1><P>
少林足球: 6.0<BR>
大话西游: 1.5<BR>
<P>You owe <EM>7.5</EM><P>
On this rental you earned <EM>3</EM> frequent renter points<P>
证明重构没有错,结果跟上次的一样
下面是完整的代码:
Movie
package com.mkfree.refactoring.shap1;
/**
* 电影类
*
* @author hk
*
* 2012-12-25 下午10:55:14
*/
public class Movie {
public static final int CHILDRENS = 2;
public static final int REGULAR = 0;
public static final int NEW_RELEASE = 1;
public Movie(String title, int priceCode) {
this.title = title;
this.priceCode = priceCode;
}
private String title;
private int priceCode;
public String getTitle() {
return title;
}
public int getPriceCode() {
return priceCode;
}
/**
* 获取收费
*
* @param daysRented
* @return
*/
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;
}
int getFrequentRenterPoints(int daysRented) {
if ((getPriceCode() == Movie.NEW_RELEASE) && daysRented > 1)
return 2;
else
return 1;
}
}
Rental
package com.mkfree.refactoring.shap1;
/**
* 租凭
*
* @author hk
*
* 2012-12-25 下午10:57:00
*/
public class Rental {
private Movie movie;
private int daysRented;
public Rental(Movie movie, int daysRented) {
this.movie = movie;
this.daysRented = daysRented;
}
public Movie getMovie() {
return movie;
}
public int getDaysRented() {
return daysRented;
}
/**
* 把代码迁移到movie中
*
* @return
*/
double getCharge() {
return this.getMovie().getCharge(daysRented);
}
/**
* 获取经常的租赁
*
* @return
*/
int getFrequentRenterPoints() {
return this.getMovie().getFrequentRenterPoints(daysRented);
}
}
Customer
package com.mkfree.refactoring.shap1;
import java.util.Enumeration;
import java.util.Vector;
/**
* 顾客
*
* @author hk
*
* 2012-12-25 下午10:59:03
*/
public class Customer {
private String name;
private Vectorrentals = new Vector<>();
public Customer(String name) {
this.name = name;
}
/**
* 添加
*
* @param rental
*/
public void addRentals(Rental rental) {
rentals.add(rental);
}
public String getName() {
return name;
}
/**
* 通计清单
*
* @return
*/
public String statement() {
Enumerationenu_rentals = rentals.elements();
String result = "Rental Record for " + this.getName() + " \n";
while (enu_rentals.hasMoreElements()) {
Rental each = enu_rentals.nextElement();
result += "\t" + each.getMovie().getTitle() + "\t" + String.valueOf(each.getCharge()) + "\n";
}
result += "Amount owed is " + String.valueOf(getTotalCharge()) + "\n";
result += "You earned " + String.valueOf(getTotalFrequentRenterPoints()) + " frequent renter points";
return result;
}
// 此即所谓query method
private double getTotalCharge() {
double result = 0;
Enumerationenu_rentals = rentals.elements();
while (enu_rentals.hasMoreElements()) {
Rental each = (Rental) enu_rentals.nextElement();
result += each.getCharge();
}
return result;
}
// 此即所谓query method
private int getTotalFrequentRenterPoints() {
int result = 0;
Enumerationenu_rentals = rentals.elements();
while (enu_rentals.hasMoreElements()) {
Rental each = (Rental) enu_rentals.nextElement();
result += each.getFrequentRenterPoints();
}
return result;
}
}
Client
package com.mkfree.refactoring.shap1;
import org.junit.Test;
public class Client {
@Test
public void testStatement() {
Movie movie1 = new Movie("少林足球", 1);
Rental rental1 = new Rental(movie1, 2);
Movie movie2 = new Movie("大话西游", 2);
Rental rental2 = new Rental(movie2, 3);
Customer customer = new Customer("oyhk");
customer.addRentals(rental1);
customer.addRentals(rental2);
String statement = customer.statement();
System.out.println(statement);
System.out.println("------------------------------------------------");
String htmlStatment = customer.htmlStatement();
System.out.println(htmlStatment);
}
}
由于重构了Movie代码,对于Client当然也修改了一点点了...