#
现在我运用同样手法处理getFrequentRenterPoints()。重构前的样子如下:
class Movie...
int getFrequentRenterPoints(int daysRented) {
if((getPriceCode() == Movie.NEW_RELEASE) && daysRented > 1)
return 2;
else
return 1;
}
首先我把这个函数移到Price class里头:
class Movie...
int getFrequentRenterPoints(int daysRented) {
return _price.getFrequentPoints(daysRented);
}
class Price...
int getFrequentRenterPoints(int daysRented) {
if((getPriceCode() == Movie.NEW_RELEASE) && daysRented > 1)
return 2;
else
return 1;
}
但是这一次我不把superclass函数声明为abstract。我只是为[新片类型]产生一个覆写函数(override method),并在superclass内留下一个已定义的函数,使它成为一种缺省行为。
class NewReleasePrice
int getFrequentRenterPoints(int daysRented) {
return (daysRented > 1) ? 2 : 1;
}
class Price...
int getFrequentRenterPoints(int daysRented) {
return 1;
}
现在我要对getCharge()实施Move Method(142).下面是重构前的代码:
class Movie...
double getCharge(int daysRented) {
double result = 0;
switch(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 Movie...
double getCharge(int daysRented) {
return _price.getCharge(daysRented);
}
class Price...
double getCharge(int daysRented) {
double result = 0;
switch(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;
}
搬移之后,我就可以开始运用Replace Conditional with Polymorphism(255)了。
下面是重构前的代码:
class Price...
double getCharge(int daysRented) {
double result = 0;
switch(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;
}
我的作法是一次取出一个case分支,在相应的class内建一个覆写函数(override method)。先从RegularPrice开始:
class RegularPrice...
double getCharge(int daysRented) {
double result = 2;
if(daysRented > 2)
result += (daysRented - 2) * 1.5;
return result;
}
class ChildernsPrice...
double getCharge(int daysRented) {
double result = 1.5;
if(daysRented > 3)
result += (daysRented - 3) * 1.5;
return result;
}
class NewReleasePrice...
double getCharge(int daysRented) {
return daysRented * 3;
}
处理完所有case分支之后,我就把Price.getCharge()声明为abstract:
class Price...
abstract double getCharge(int daysRented);
首先我要使用Replace Type Code with State/Strategy(227).第一步骤是针对[与型别相依的行为]使用Self Encapsulate Field(171),确保任何时候都通过getting和setting两个函数来运用这些行为。由于多数代码来自其他classes,所以多数函数都已经使用getting函数。但构造函数(constructor)仍然直接访问价格代码:
class Movie...
public Movie(String name, int priceCode) {
_title = name;
_priceCode = priceCode;
}
我可以用一个setting函数来代替:
class Movie...
public Movie(String name, int priceCode) {
_title = name;
setPriceCode(priceCode);
}
现在我加入新class,并在price对象中提供[与型别相依的行为]。为了实现这一点,我在Price内加入一个抽象函数(abstract method),并在其所有subclasses中加上对应的具体函数(concrete method):
abstract class Price {
abstract int getPriceCode(); //取得价格代号
}
class ChildernsPrice extends Price {
int getPriceCode() {
return Movie.CHILDERNS;
}
}
class NewReleasePrice extends Price {
int getPriceCode() {
return Movie.NEW_RELEASE;
}
}
class RegularPrice extends Price {
int getPriceCode() {
return Movie.REGULAR;
}
}
现在,我需要修改Movie class内的[价格代号]访问函数(get/set函数,如下),让它们使用新class。下面是重构前的样子:
public int getPriceCode() {
return _priceCode;
}
public void setPriceCode(int arg) {
_priceCode;
}
private int _priceCode;
这个意味我必须在Movie class内保存一个price对象,而不再是保存一个_priceCode变量。此外我还需要修改访问函数:
class Movie...
public int getPriceCode() { //取得价格代号
return _price.getPriceCode();
}
public void setPriceCode(int arg) { //设定价格代码
switch(arg) {
case REGULAR: //普通片
_price = new RegularPrice();
break;
case CHILDERNS: //儿童片
_price = new ChildernsPrice();
break;
case NEW_RELEASE: //新片
_price = new NewReleasePrice();
break;
default:
throw new IllegalArument Exception("Incorrect Price Code");
}
}
private Price _price;
终于。。。。。我们来到继承(inheritance)
我们有数种影片类型,它们以不同的方式回答相同的问题。这听起来很像subclasses的工作。我们可以建立Movie的三个subclasses,每个都有自己的计费法。
这么一来我就可以运用多态(polymorphism)来取代switch语句了。很遗憾的是这里有个小问题,不能这么干。一部影片可以在生命期周期内修
改自己的分类,一个对象却不能在生命周期内修改自己的分类,一个对象却不能在生命周期内修改自己所属的class。不过还是有一个解决方法:state pattern(模式)。加入这一层间接性,我们就可以在Price对象内进行subclassing动作,于是便可在任何必要时刻修改价格。
为了引入state模式,我使用三个重构准则。首先运用Replace Type Code with State/Strategy(227),将[与型相依的行为](type code behavior)搬移至state模式内。然后运用Move Method(142)将switch语句移到Price class里头。最后运用Replace Conditional with Polymorphism(255)去掉switch语句。
运用多态(polymorphism)取代与价格相关的条件逻辑
这个问题的第一部分是switch语句。在另一个对象的属性(attribute)基础上运用switch语句,并不是什么好注意。如果不得不使用,也应该在对象自己的数据上使用,而不是在别人的数据上使用。
class Rental...
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 class里头去:
class Movie...
double getCharge(int daysRented) {
double result = 0;
switch(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;
}
为了让它得以运作,我必须把[租期长度]作为参数传递进去。当然,[租期长度]来自
Rental对象.计算费用时需要两份数据:[租期长度]和[影片类型]。为什么我选择[将租期长度传给Movie对象]而不是[将影片类型传给
Rental对象]呢?因为本系统可能发生的变化是加入新影片类型,这种变化带有不稳定倾向。如果影片类型有所变化,我希望掀起最小的涟漪,所以我选择在
Movie对象内计算费用。
我把上述计费方法放进Movie class里头,然后修改Rental的getCharge(),让它使用这个新函数:
class Rental...
double getCharge() {
return _movie.getCharge(_daysRented);
}
很明显看出来,htmlStatement()和statement()是不同的。现在,我应该脱下[重构]的帽子,戴上[添加功能]的帽子,戴上[添加功能]的帽子。我可以像下面这样编写htmlStatement(),并添加相应测试:
public String htmlStatement() {
Enumeration rentals = _rentals.elements();
String result = "<H1>Rental Record for <EM> " + getName() + "</EM></H1><P>\n";
while(rentals.hasMoreElements()) {
Rental each = (Rental)rentals.nextElement(); //取得一笔租借记录
result += each.getMovie().getTitle() + ":" +
String.valueOf(each.getCharge()) + "<BR>\n";
}
// add footer lines(结尾打印)
result += "<P>You owe <EM>" + String.valueOf(getTotalCharge()) + "</EM><P> \n";
result += "On this rental you earned <EM>" + String.valueOf(getTotalfrequentRenterPoints()) +
"</EM>frequent renter points<P>";
return result;
}
通过计算逻辑的提炼,我可以完成一个htmlStatement(),并复用(reuse)原本statement()内的所有计算。我不必剪剪贴贴,所以如果计算规则发生改变,我只需在程序中做一处修改。
然后以同样手法处理frequentRenterPoints:
class Customer...
public String statement() {
int frequentRenterPoints = 0;
Enumeration rentals = _rentals.elements();
String result = "Rental Record for * " + getName() + "\n";
while(rentals.hasMoreElements()) {
Rental each = (Rental)rentals.nextElement(); //取得一笔租借记录
frequentRenterPoints += each.getFrequentRenterPoints();
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(frequentRenterPoints) +
"frequent renter points";
return result;
}
--------------------------------------------------------------------------------------------------------------------
public String statement() {
Enumeration rentals = _rentals.elements();
String result = "Rental Record for * " + getName() + "\n";
while(rentals.hasMoreElements()) {
Rental each = (Rental)rentals.nextElement(); //取得一笔租借记录
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;
}
// 译注:此即所谓query method
private int getTotalFrequentRenterPoints() {
int result = 0;
Enumeration rentals = _rentals.elements();
while(rentals.hasMoreElements()) {
Rental each = (Rental)rentals.nextElement();
result += each.getFrequentRenterPoints();
}
return result;
}
正如我在前面提过的,临时变量可能是个问题。它们只在自己所属的函数中有效,所以它们会助长[冗长而复杂]的函数。这里我们有两个临时变量,两者都是用来
从Customer对象相关的Rental对象中获得某个总量。不论ASCII版或HTML版都需要这些总量。我打算运用Replace Temp with Query(120),并利用所谓的query method来取代totalAmount或frequentRentalPoints这两个临时变量。由于class内的任何函数都可以取用(调用)上述所谓query methods,所以它可能够促进较干净的设计,而非冗长复杂的函数:
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(); //取得一笔租借记录
frequentRenterPointers += each.getFrequentRenterPoints();
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;
}
首先我以Customer class的getTotalCharge()取代totalAmount:
class Customer...
public String statement() {
int frequentRenterPoints = 0;
Enumeration rentals = _rentals.elements();
String result = "Rental Record for * " + getName() + "\n";
while(rentals.hasMoreElements()) {
Rental each = (Rental)rentals.nextElement(); //取得一笔租借记录
frequentRenterPointers += each.getFrequentRenterPoints();
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(frequentRenterPoints) +
"frequent renter points";
return result;
}
// 译注:次即query method
private double getTotalCharge() {
double result = 0;
Enumeration rentals = _rentals.elements();
while(rentals.hasMoreElements()) {
Rental each = (Rental)rentals.nextElement();
result += each.getCharge();
}
return result;
}
这并不是Replace Temp with Query(120)的最简单情况。由于totalAmount在循环内部被赋值,我不得不把循环复制到query method中。
提取[常客积点计算]代码
首先我们需要针对[常客积点计算]这部分代码(以下粗体部分)运用Extract Method(110)重构准则:
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 ++;
if((each.getMovie().getPriceCode() == Movie.NEW_RELEASE) &&
each.getDaysRented() > 1)
frequentRenterPoints ++;
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;
}
再一次我又要寻找局部变量。这里再一次用到了each,而它可以被当作参数传入新函数中。另一个临时变量是frequentRenterPoints。本
例中的它在被使用之前已经先有初值,但提炼出来的函数并没有读取改值,所以我们不需要将它当作参数传进去,只需对它执行[付添赋值操作](appending assignment,operator+=)就行了。
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(); //取得一笔租借记录
frequentRenterPointers += each.getFrequentRenterPoints();
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 getFrequentRenterPoints() {
if((getMovie().getPriceCode() == Movie.NEW_RELEASE)
&& getDaysRented() > 1)
return 2;
else
return 1;
}
Customer.statement():
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();
// add frequent renter points(累加常客积点)
frequentRenterPoints ++;
if((each.getMovie().getPriceCode() == Movie.NEW_RELEASE) &&
each.getDaysRented() > 1)
frequentRenterPoints ++;
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;
}
下一件引我注意的事时:thisAmount如今变成多余了.它接受each.getCharge()的执行结果,然后就不再有任何改变.所以我可以运用Replace Temp with Query(120)把thisAmount除去:
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 ++;
if((each.getMovie().getPriceCode() == Movie.NEW_RELEASE) &&
each.getDaysRented() > 1)
frequentRenterPoints ++;
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 class中被优化.而且如果代码有合理的组织和管理,优化会有很好的效果.
|