1.1 起点
Movie(影片)
public class Movie {
public static final int CHILDRENS = 2;
public static final int REGULAR = 0;
public static final int NEW_RELEASE = 1;
private String _title;
private int _priceCode;
public Movie(String _title, int code) {
this._title = _title;
_priceCode = code;
}
public int getPriceCode() {
return _priceCode;
}
public void setPriceCode(int code) {
_priceCode = code;
}
public String get_title() {
return _title;
}
}
Rental(租赁)
class Rental {
private Movie _movie;
private int _daysRented;
public Rental(Movie movie, int daysRented) {
_movie = movie;
_daysRented = daysRented;
}
public Movie getMovie() {
return _movie;
}
public int getDaysRented() {
return _daysRented;
}
}
Customer(顾客)
import java.util.Enumeration;
import java.util.Vector;
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;
}
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();
/**
* 租碟的价格是根据碟的类型进行计算的,
* 一般的碟:
* 2块钱起租赁,超过两天每天1块五
* 新碟:
* 每天三块
* 儿童碟:
* 不足四天都是1.5,超过四天(包括)每天1.5
*/
//determine amounts for each line
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;
}
}
// add frequent renter points
frequentRenterPoints ++;
// add bonus for a two day new release rental
if ((each.getMovie().getPriceCode() == Movie.NEW_RELEASE) && each.getDaysRented() > 1) {
frequentRenterPoints ++;
}
// show figures fo 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;
}
}
statement()
是生成详单的函数,交互图如下:
测试代码
public class CustomerTest {
@org.junit.Test
public void statement() {
//电影
Movie m1 = new Movie("电影1", Movie.CHILDRENS);
Movie m2 = new Movie("电影2", Movie.CHILDRENS);
Movie m3 = new Movie("电影3", Movie.CHILDRENS);
Movie m4 = new Movie("电影4", Movie.CHILDRENS);
Movie m5 = new Movie("电影5", Movie.CHILDRENS);
Movie m6 = new Movie("电影6", Movie.NEW_RELEASE);
Movie m7 = new Movie("电影7", Movie.NEW_RELEASE);
Movie m8 = new Movie("电影8", Movie.REGULAR);
Movie m9 = new Movie("电影9", Movie.REGULAR);
Movie m10 = new Movie("电影10", Movie.REGULAR);
Movie m11 = new Movie("电影11", Movie.REGULAR);
Customer customer = new Customer("Mike");
customer.addRental(new Rental(m1,1));
customer.addRental(new Rental(m2,2));
customer.addRental(new Rental(m3,3));
customer.addRental(new Rental(m4,4));
customer.addRental(new Rental(m5,5));
customer.addRental(new Rental(m6,1));
customer.addRental(new Rental(m7,2));
customer.addRental(new Rental(m8,1));
customer.addRental(new Rental(m9,2));
customer.addRental(new Rental(m10,3));
customer.addRental(new Rental(m11,4));
String exepectedResult =
"Rental Record for Mike\n" +
"\t电影1\t1.5\n" +
"\t电影2\t1.5\n" +
"\t电影3\t1.5\n" +
"\t电影4\t3.0\n" +
"\t电影5\t4.5\n" +
"\t电影6\t3.0\n" +
"\t电影7\t6.0\n" +
"\t电影8\t2.0\n" +
"\t电影9\t2.0\n" +
"\t电影10\t3.5\n" +
"\t电影11\t5.0\n" +
"Amount owed is 33.5\n" +
"You earned 12 frequent renter points";
org.junit.Assert.assertEquals(exepectedResult, customer.statement());
}
}
对此起始程序的评价
不符合面向对象精神。
Customer#statement
做的事情实在太多了,它做了很多原本应该由其他类完成的事情。
新需求: 以HTML格式输出详单;计费标准;改变影片分类规则;
无法适应新需求,一旦有新的需求,代码改动太大或所做的改动容易引入bug。
一个方法不能过长,不能超过一屏;
代码不能出现两次;
需要为程序添加新特性,代码结构使你很难达成目的,先重构――使新特性的添加比较容易,任何再添加新特性。
1.2 重构的第一步
第一个步骤:为即将修改的代码建立一组可靠的测试环境。
让测试有能力自我检测。
好的测试是重构的根本。
花时间建立一个优良的测试机制是完全值得的。
1.3 分解并重组statement()
代码块愈小,代码的功能就愈容易管理,代码的处理和移动也就愈轻松。
步骤1:找出代码的逻辑泥团并运用Extract Method。
首先,找出函数内的局部变量和参数;
each
:未被修改
thisAmount
:被修改
任何不会被修改的变量可以被当成参数传入新的函数。
如果只有一个变量会被修改,可以把它当做返回值。
以下代码:statement()
提炼出amountFor()
方法。
import java.util.Enumeration;
import java.util.Vector;
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;
}
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();
thisAmount = amountFor(each);
// add frequent renter points
frequentRenterPoints ++;
// add bonus for a two day new release rental
if ((each.getMovie().getPriceCode() == Movie.NEW_RELEASE) && each.getDaysRented() > 1) {
frequentRenterPoints ++;
}
// show figures fo 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;
}
private double amountFor(Rental each) {
double thisAmount = 0;
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;
}
}
return thisAmount;
}
}
使用IDE提供的提取函数功能就可以做到。
重构步骤的本质:由于每次修改的幅度都很小,所以任何错误都很容易发现。
重构技术就是以微小的步伐修改程序。如果你犯下错误,很容易便可发现它。
修改amountFor()
局部变量名称
private double amountFor(Rental aRental) {
double result = 0;
switch (aRental.getMovie().getPriceCode()) {
case Movie.REGULAR:
result += 2;
if (aRental.getDaysRented() > 2) {
result += (aRental.getDaysRented() - 2) * 1.5;
}
break;
case Movie.NEW_RELEASE:
result += aRental.getDaysRented() * 3;
break;
case Movie.CHILDRENS:
result += 1.5;
if (aRental.getDaysRented() > 3){
result += (aRental.getDaysRented() - 3) * 1.5;
break;
}
}
return result;
}
好的代码应该清楚表达出自己的功能,变量名称是代码清晰的关键。
任何一个傻瓜都能写出计算机可以理解的代码。唯有写出人类容易理解的代码,才是优秀的程序员。
代码应该表现自己的目的。
搬移“金额计算”代码
amountFor()
使用了Rental
类的信息,却没有使用Customer
类的。
函数应该放在它所使用的数据的所属对象内。
将amountFor()
移到Rental
类:Move Method。
class Rental {
private Movie _movie;
private int _daysRented;
public Rental(Movie movie, int daysRented) {
_movie = movie;
_daysRented = daysRented;
}
public Movie getMovie() {
return _movie;
}
public int getDaysRented() {
return _daysRented;
}
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;
}
}
去掉参数,变更函数名称。
class Customer {
.....
private double amountFor(Rental aRental) {
return aRental.getCharge();
}
}
下一个步骤:找出程序中对于旧函数的所有引用点,并修改它们,让它们改用新函数。
class Customer {
....
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();
thisAmount = each.getCharge();
.....
}
.....
}
下一个步骤:去掉旧函数;
如果旧函数是一个public
函数,而你又不想修改其他类的接口,可以保留旧函数,让它调用新函数。
下一个步骤:thisAmount
变得多余了。它接受each.getCharge()
的执行结果,然后就不再有任何变化。
运用Replace Temp with Query把thisAmount
除去:
class Customer {
....
public String statement(){
double totalAmount = 0;
int frequentRenterPoints = 0;
Enumeration rentals = _rentals.elements();
String result = "Rental Record for " + getName() + "\n";
while (rentals.hasMoreElements()) {
Rental each = (Rental) rentals.nextElement();
// add frequent renter points
frequentRenterPoints ++;
// add bonus for a two day new release rental
if ((each.getMovie().getPriceCode() == Movie.NEW_RELEASE) && each.getDaysRented() > 1) {
frequentRenterPoints ++;
}
// show figures fo this rental
result += "\t" + each.getMovie().getTitle() + "\t" + String.valueOf(each.getCharge()) + "\n";
totalAmount += each.getCharge();
}
//add footer lines
result += "Amount owed is " + String.valueOf(totalAmount) + "\n";
result += "You earned " + String.valueOf(frequentRenterPoints) + " frequent renter points";
return result;
}
}
尽量除去这一类临时变量。临时变量往往容易引发问题,它们会导致大量参数被传来传去,而其实完全没有这种必要。你很容易跟丢它们。
这么做也需付出性能上的代价,例如本例费用就被计算了两次。但是这很容易在Rental
类中被优化。而且如果代码有合理的组织和管理,优化就会有很好的效果。
提炼“常客积分计算”代码
首先:运用Extract Method:
局部变量:each
作为参数传入新函数;
临时变量:frequentRenterPoints
:使用前已经有初值,但提炼出来的函数并没有读取该值,所以不需要将它当做参数传进去,只需把新函数的返回值累加上去就行了。
函数提炼->重新编译测试->搬移->编译测试
class Customer {
...
public String statement(){
double totalAmount = 0;
int frequentRenterPoints = 0;
Enumeration rentals = _rentals.elements();
String result = "Rental Record for " + getName() + "\n";
while (rentals.hasMoreElements()) {
Rental each = (Rental) rentals.nextElement();
frequentRenterPoints += each.getFrequenterPoints();
// show figures fo this rental
result += "\t" + each.getMovie().getTitle() + "\t" + String.valueOf(each.getCharge()) + "\n";
totalAmount += each.getCharge();
}
//add footer lines
result += "Amount owed is " + String.valueOf(totalAmount) + "\n";
result += "You earned " + String.valueOf(frequentRenterPoints) + " frequent renter points";
return result;
}
}
class Rental {
...
int getFrequenterPoints() {
if ((getMovie().getPriceCode() == Movie.NEW_RELEASE) && getDaysRented() > 1) {
return 2;
} else
return 1;
}
}
去除临时变量
临时变量只在自己所属的函数中有效,所以它们会助长冗长而复杂的函数。
运用Replace Temp with Query,并利用查询函数(query method)来取代totalAmount
和frequentRentalPoints
这两个临时变量。
由于类中的任何函数都可以调用上述查询函数,所以它能够促成较干净的设计,而减少冗长复杂的函数。
以下代码是去除totalAmount
和frequentRentalPoints
变量之后的:
class Customer {
...
public String statement(){
Enumeration rentals = _rentals.elements();
String result = "Rental Record for " + getName() + "\n";
while (rentals.hasMoreElements()) {
Rental each = (Rental) rentals.nextElement();
// show figures fo this rental
result += "\t" + each.getMovie().getTitle() + "\t" + String.valueOf(each.getCharge()) + "\n";
}
//add footer lines
result += "Amount owed is " + String.valueOf(getTotalCharge()) + "\n";
result += "You earned " + String.valueOf(getTotalFrequentRenterPoints()) + " frequent renter points";
return result;
}
private double getTotalCharge() {
double result = 0;
Enumeration rentals = _rentals.elements();
while (rentals.hasMoreElements()) {
Rental each = (Rental) rentals.nextElement();
result += each.getCharge();
}
return result;
}
private int getTotalFrequentRenterPoints() {
int result = 0;
Enumeration rentals = _rentals.elements();
while (rentals.hasMoreElements()) {
Rental each = (Rental) rentals.nextElement();
result += each.getFrequenterPoints();
}
return result;
}
}
到目前为止的代码:
public class Movie {
public static final int CHILDRENS = 2;
public static final int REGULAR = 0;
public static final int NEW_RELEASE = 1;
private String _title;
private int _priceCode;
public Movie(String _title, int code) {
this._title = _title;
_priceCode = code;
}
public int getPriceCode() {
return _priceCode;
}
public void setPriceCode(int code) {
_priceCode = code;
}
public String getTitle() {
return _title;
}
}
class Rental {
private Movie _movie;
private int _daysRented;
public Rental(Movie movie, int daysRented) {
_movie = movie;
_daysRented = daysRented;
}
public Movie getMovie() {
return _movie;
}
public int getDaysRented() {
return _daysRented;
}
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;
}
int getFrequenterPoints() {
if ((getMovie().getPriceCode() == Movie.NEW_RELEASE) && getDaysRented() > 1) {
return 2;
} else
return 1;
}
}
import java.util.Enumeration;
import java.util.Vector;
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;
}
public String statement(){
Enumeration rentals = _rentals.elements();
String result = "Rental Record for " + getName() + "\n";
while (rentals.hasMoreElements()) {
Rental each = (Rental) rentals.nextElement();
// show figures fo this rental
result += "\t" + each.getMovie().getTitle() + "\t" + String.valueOf(each.getCharge()) + "\n";
}
//add footer lines
result += "Amount owed is " + String.valueOf(getTotalCharge()) + "\n";
result += "You earned " + String.valueOf(getTotalFrequentRenterPoints()) + " frequent renter points";
return result;
}
private double getTotalCharge() {
double result = 0;
Enumeration rentals = _rentals.elements();
while (rentals.hasMoreElements()) {
Rental each = (Rental) rentals.nextElement();
result += each.getCharge();
}
return result;
}
private int getTotalFrequentRenterPoints() {
int result = 0;
Enumeration rentals = _rentals.elements();
while (rentals.hasMoreElements()) {
Rental each = (Rental) rentals.nextElement();
result += each.getFrequenterPoints();
}
return result;
}
}
public class CustomerTest {
@org.junit.Test
public void statement() {
//电影
Movie m1 = new Movie("电影1", Movie.CHILDRENS);
Movie m2 = new Movie("电影2", Movie.CHILDRENS);
Movie m3 = new Movie("电影3", Movie.CHILDRENS);
Movie m4 = new Movie("电影4", Movie.CHILDRENS);
Movie m5 = new Movie("电影5", Movie.CHILDRENS);
Movie m6 = new Movie("电影6", Movie.NEW_RELEASE);
Movie m7 = new Movie("电影7", Movie.NEW_RELEASE);
Movie m8 = new Movie("电影8", Movie.REGULAR);
Movie m9 = new Movie("电影9", Movie.REGULAR);
Movie m10 = new Movie("电影10", Movie.REGULAR);
Movie m11 = new Movie("电影11", Movie.REGULAR);
Customer customer = new Customer("Mike");
customer.addRental(new Rental(m1,1));
customer.addRental(new Rental(m2,2));
customer.addRental(new Rental(m3,3));
customer.addRental(new Rental(m4,4));
customer.addRental(new Rental(m5,5));
customer.addRental(new Rental(m6,1));
customer.addRental(new Rental(m7,2));
customer.addRental(new Rental(m8,1));
customer.addRental(new Rental(m9,2));
customer.addRental(new Rental(m10,3));
customer.addRental(new Rental(m11,4));
String exepectedResult =
"Rental Record for Mike\n" +
"\t电影1\t1.5\n" +
"\t电影2\t1.5\n" +
"\t电影3\t1.5\n" +
"\t电影4\t3.0\n" +
"\t电影5\t4.5\n" +
"\t电影6\t3.0\n" +
"\t电影7\t6.0\n" +
"\t电影8\t2.0\n" +
"\t电影9\t2.0\n" +
"\t电影10\t3.5\n" +
"\t电影11\t5.0\n" +
"Amount owed is 33.5\n" +
"You earned 12 frequent renter points";
org.junit.Assert.assertEquals(exepectedResult, customer.statement());
}
}
1.4 运用多态取代与价格相关的条件逻辑
用户准备修改影片分类规则,为了让费用计算和常客积分计算逻辑更加修改,特意进行重构。
最好不要在另一个对象的属性基础上运用switch
语句。如果不得不使用,也应该在对象自己的数据上使用,而不是在别人的数据上使用。
把getCharge()
移到Movie
类里。
public class Movie {
...
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
对象。计算费用时需要两项数据:租期长度和影片类型。为什么选择将租期长度传给Movie
对象,而不是将影片类型传给Rental
对象呢?因为本系统可能发生的变化是加入新影片类型,这种变化带有不稳定倾向。如果影片类型有所变化,需要尽量控制它造成的影响,所以选择在Movie
对象内计算费用。
把计费方法放进Movie
类,然后修改Rental
的getCharge()
,让它使用这个新函数:
class Rental {
...
double getCharge() {
return _movie.getCharge(_daysRented);
}
...
}
处理常客积分计算:
class Rental {
...
int getFrequenterPoints() {
return _movie.getFrequenterPoints(_daysRented);
}
}
public class Movie {
...
int getFrequenterPoints(int daysRented) {
if ((getPriceCode() == Movie.NEW_RELEASE) && daysRented > 1) {
return 2;
} else
return 1;
}
}
这样就把根据影片类型而变化的所有东西,都放到了影片类型所属的类中。
终于...我们来到继承
有数种影片类型,它们以不同的方式回答相同的问题。
这样一来,可以用多态来替换
switch
语句了。但是不能这么干,因为一部影片可以在生命周期内修改自己的分类,一个对象却不能在生命周期内修改自己所属的类。 可以考虑State模式。
这是一个State模式还是一个Strategy模式?答案取决于Price
类究竟代表计费方式,还是代表影片的某个状态。
引入State模式的重构手法:
首先运用Replace Type Code with State/Strategy,将与类型相关的行为搬移至State模式内。然后运行Move Method将switch
语句移到Price
类。最后运用Replace Conditional with Polymorphism去掉switch
语句。
首先运用Replace Type Code with State/Strategy。第一步骤是针对类型代码使用Self Encapsulate Field,确保任何时候都通过取值函数和设置函数来访问类型代码。
public class Movie {
....
public Movie(String _title, int code) {
this._title = _title;
setPriceCode(code);
}
....
}
加入Price
类,并在其中提供类型相关的行为。
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
类内的“价格代号”访问函数,让它们使用新类。
在Movie
类中使用Price
对象。
public class Movie {
public static final int CHILDRENS = 2;
public static final int REGULAR = 0;
public static final int NEW_RELEASE = 1;
private String _title;
private Price _price;
public Movie(String _title, int code) {
this._title = _title;
setPriceCode(code);
}
public int getPriceCode() {
return _price.getPriceCode();
}
public void setPriceCode(int code) {
switch (code) {
case REGULAR:
_price = new RegularPrice();
break;
case CHILDRENS:
_price = new ChildrensPrice();
break;
case NEW_RELEASE:
_price = new NewReleasePrice();
break;
default:
throw new IllegalArgumentException("Incorrect Price Code");
}
}
public String getTitle() {
return _title;
}
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 getFrequenterPoints(int daysRented) {
if ((getPriceCode() == Movie.NEW_RELEASE) && daysRented > 1) {
return 2;
} else
return 1;
}
}
对Movie#getCharge()
实施Move Method。
public class Movie {
...
double getCharge(int daysRented) {
return _price.getCharge(daysRented);
}
....
}
public abstract class Price {
abstract int getPriceCode();
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;
}
}
运用Replace Conditional with Polymorphism。
public abstract class Price {
abstract int getPriceCode();
abstract double getCharge(int daysRented);
}
public class RegularPrice extends Price {
@Override
int getPriceCode() {
return Movie.REGULAR;
}
double getCharge(int daysRented) {
double result = 2;
if (daysRented > 2) {
result += (daysRented - 2) * 1.5;
}
return result;
}
}
public class NewReleasePrice extends Price {
@Override
int getPriceCode() {
return Movie.NEW_RELEASE;
}
double getCharge(int daysRented) {
return daysRented * 3;
}
}
public class ChildrensPrice extends Price {
@Override
int getPriceCode() {
return Movie.CHILDRENS;
}
double getCharge(int daysRented) {
double result = 1.5;
if (daysRented > 3) {
result += (daysRented - 3) * 1.5;
}
return result;
}
}
运用相同手法处理Movie#getFrequenterPoints()
。
把这个函数移到Price
类。
public class Movie {
...
int getFrequenterPoints(int daysRented) {
return _price.getFrequenterPoints(daysRented);
}
}
public abstract class Price {
abstract int getPriceCode();
abstract double getCharge(int daysRented);
int getFrequenterPoints(int daysRented) {
if ((getPriceCode() == Movie.NEW_RELEASE) && daysRented > 1) {
return 2;
} else
return 1;
}
}
不把超类函数声明为abstract
,为新片类型增加一个覆写函数,并在超类中留下一个已定义的函数,使它成为一种默认行为。
public class NewReleasePrice extends Price {
@Override
int getPriceCode() {
return Movie.NEW_RELEASE;
}
double getCharge(int daysRented) {
return daysRented * 3;
}
@Override
int getFrequenterPoints(int daysRented) {
return (daysRented > 1)? 2 : 1;
}
}
public abstract class Price {
abstract int getPriceCode();
abstract double getCharge(int daysRented);
int getFrequenterPoints(int daysRented) {
return 1;
}
}
引入State模式的好处:如果要修改任何与价格有关的行为,或是添加新的定价标准,或是加入其它取决于价格的行为,程序的修改会容易很多。
1.5 结语
重构使责任的分配更合理,代码的维护更轻松。
重构的节奏:测试、小修改、测试、小修改、测试、小修改...