上一篇文章:java 代码重构-第一章(提炼代码)
说到了代码的提炼,把一些代码提炼出来写成一个方法,然后再去调用它,好了不多说了,想了解看上一篇吧
去除临时变量
正如我在前面提过的,临时变量可能是个问题。它们只在自己所属的函数中有效,所以它们会助长「冗长而复杂」的函数。这里我们有两个临时变量,两者都是用来从Customer 对象相关的Rental 对象中获得某个总量。不论ASCII 版或HTML 版都需要这些总量。我打算运用 Replace Temp with Query,并利用所谓的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();
frequentRenterPoints += each.getFrequentRenterPoints();
// show figures for 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;
}
}
我以Customer class 的getTotalCharge() 取代totalAmount 。
代码变成下面:
class Customer {
...
/**
* 通计清单
*
* @return
*/
public String statement() {
int frequentRentePoints = 0;
Enumeration<Rental> enu_rentals = rentals.elements();
String result = "Rental Record for " + this.getName() + " \n";
while (enu_rentals.hasMoreElements()) {
Rental each = enu_rentals.nextElement();
frequentRentePoints += each.getFrequentRenterPoints();
result += "\t" + each.getMovie().getTitle() + "\t" + String.valueOf(each.getCharge()) + "\n";
}
result += "Amount owed is " + String.valueOf(getTotalCharge()) + "\n";
result += "You earned " + String.valueOf(frequentRentePoints) + " frequent renter points";
return result;
}
// 译注:此即所谓query method
private double getTotalCharge() {
double result = 0;
Enumeration<Rental> enu_rentals = rentals.elements();
while (enu_rentals.hasMoreElements()) {
Rental each = (Rental) enu_rentals.nextElement();
result += each.getCharge();
}
return result;
}
}
重构之后,重新编译并测试,然后以同样手法处理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();
//show figures for this rental
result += "\t" + each.getMovie().getTitle()+ "\t" +
}
//add footer lines
result += "Amount owed is " + String.valueOf(getTotalCharge()) + "\n";
result += "You earned " + String.valueOf(frequentRenterPoints) +
" frequent renter points";
return result;
}
添加方法代码如下:
class customer ...
/**
* 通计清单
*
* @return
*/
public String statement() {
Enumeration<Rental> enu_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;
Enumeration<Rental> enu_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;
Enumeration<Rental> enu_rentals = rentals.elements();
while (enu_rentals.hasMoreElements()) {
Rental each = (Rental) enu_rentals.nextElement();
result += each.getFrequentRenterPoints();
}
return result;
}
图1.8至图1.11分别以UML class diagram(类图)和interaction diagram (交互作用图)展示statement() 重构前后的变化。
对比
想一想:
做完这次重构,有必要停下来思考一下。大多数重构都会减少代码总量,但这次却增加了代码总量,那是因为Java 1.1需要大量语句(statements)来设置一个总和(summing)循环。哪怕只是一个简单的总和循环,每个元素只需一行代码,外围的支持代码也需要六行之多。这其实是任何程序员都熟悉的习惯写法,但代码数量还是太多了。
这次重构存在另一个问题,那就是性能。原本代码只执行while 循环一次,新版本要执行三次。如果循环耗时很多,就可能大大降低程序的性能。单单为了这个原因,许多程序员就不愿进行这个重构动作。但是请注意我的用词:如果和可能。除非我进行评测(profile),否则我无法确定循环的执行时间,也无法知道这个循环是否被经常使用以至于影响系统的整体性能。重构时你不必担心这些,优化时你才需要担心它们,但那时候你已处于一个比较有利的位置,有更多选择可以完成有效优化
现在,Customer class 内的任何代码都可以取用这些query methods 了,如果系统他处需要这些信息,也可以轻松地将query methods 加入Customer class 接口。如果没有这些query methods ,其他函数就必须了解Rental class,并自行建立循环。在一个复杂系统中,这将使程序的编写难度和维护难度大大增加。
你可以很明显看出来,htmlStatement() 和statement() 是不同的。现在,我应该脱下「重构」的帽子,戴上「添加功能」的帽子。我可以像下面这样编写htmlStatement() ,并添加相关测试。
htmlStatement代码
class sustumer...
/**
* 输出html清单
*
* @return
*/
public String htmlStatement() {
Enumeration<Rental> enu_rentals = rentals.elements();
String result = "<H1>Rentals for <EM>" + getName() + "</EM></ H1><P>\n";
while (enu_rentals.hasMoreElements()) {
Rental each = (Rental) enu_rentals.nextElement();
// show figures for each rental
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)原本statements() 内的所有计算。我不必剪剪贴贴,所以如果计算规则发生改变,我只需在程序中做一处修改。完成其他任何类型的报表也都很快而且很容易。这次重构并不花很多时间,其中大半时间我用来弄清楚代码所做的事,而这是我无论如何都得做的。
前述有些重构码系从ASCII 版本里头拷贝过来——主要是循环设置部分。更深入的重构动作可以清除这些重复代码。我可以把处理表头(header)、表尾(footer)和报表细目的代码都分别提炼目出来。在 Form Template Method 实例中,.你可以看到如何做这些动作。但是,现在用户又开始嘀咕了,他们准备修改影片分类规则。我们尚未清楚他们想怎么做,但似乎新分类法很快就要引入,现有的分类法马上就要变更。与之相应的费用计算方式和常客积点计算方式都还待决定,现在就对程序做修改,肯定是愚蠢的。我必须进入费用计算和常客积点计算中,把「因条件 而异的代码」(译注:指的是switch 语句内的case 子句)替换掉,这样才能为 将来的改变镀上一层保护膜。现在,请重新戴回「重构」这顶帽子。
那么下面是完整代码:
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 Vector<Rental> rentals = new Vector<>();
public Customer(String name) {
this.name = name;
}
/**
* 添加
*
* @param rental
*/
public void addRentals(Rental rental) {
rentals.add(rental);
}
public String getName() {
return name;
}
/**
* 输出html清单
*
* @return
*/
public String htmlStatement() {
Enumeration<Rental> enu_rentals = rentals.elements();
String result = "<H1>Rentals for <EM>" + getName() + "</EM></ H1><P>\n";
while (enu_rentals.hasMoreElements()) {
Rental each = (Rental) enu_rentals.nextElement();
// show figures for each rental
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;
}
/**
* 通计清单
*
* @return
*/
public String statement() {
Enumeration<Rental> enu_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;
Enumeration<Rental> enu_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;
Enumeration<Rental> enu_rentals = rentals.elements();
while (enu_rentals.hasMoreElements()) {
Rental each = (Rental) enu_rentals.nextElement();
result += each.getFrequentRenterPoints();
}
return result;
}
}
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;
private String title;
private int priceCode;
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public int getPriceCode() {
return priceCode;
}
public void setPriceCode(int priceCode) {
this.priceCode = priceCode;
}
}
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;
}
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;
}
/**
*
* @return
*/
int getFrequentRenterPoints() {
if ((getMovie().getPriceCode() == Movie.NEW_RELEASE) && getDaysRented() > 1)
return 2;
else
return 1;
}
}
Client
package com.mkfree.refactoring.shap1;
import org.junit.Test;
public class Client {
@Test
public void testStatement() {
Movie movie1 = new Movie();
movie1.setTitle("少林足球");
movie1.setPriceCode(1);
Rental rental1 = new Rental(movie1, 2);
Movie movie2 = new Movie();
movie2.setTitle("大话西游");
movie2.setPriceCode(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);
}
}
最后输出的结果:有两个 1 statement 2 htmlStatement