文章目录
![在这里插入图片描述](https://img-blog.csdnimg.cn/894b8da8a19a4240937a064c50e4744a.png)
重构原则
何为重构
不改变代码本身可观察的行为,进行代码的构造变化
为何重构
- 消除重复代码,方便未来的修改
- 利用重构协助理解不熟悉的代码
- 提高以后维护代码的速率
何时重构
- 添加代码时
- 修改bug时
- 复审代码质量时
重构会影响性能吗
可能会,但原则是先写出能完善的代码,再测试性能
实例
原始类
Movie类:该类主要记录类型、价格和标题等,是单纯数据类。
/**
* 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 priceCode) {
this.title = title;
this.priceCode = priceCode;
}
public int getPriceCode() {
return priceCode;
}
public void setPriceCode(int priceCode) {
this.priceCode = priceCode;
}
public String getTitle() {
return title;
}
}
Rental类:表示某位顾客租了一部影片,表示行为。
/**
* Rental表示某位顾客租了一部影片,表示行为。
*/
class Rental {
private Movie movie;
private int daysRented;
public Rental(Movie movie, int daysRented) {
this.movie = movie;
this.daysRented = daysRented;
}
public int getDaysRented() {
return daysRented;
}
public Movie getMovie() {
return movie;
}
}
Customer类:表示顾客,有数据和相应的访问函数。
/**
* Customer表示顾客,有数据和相应的访问函数
*/
public class Customer {
private String name;
private Vector rentals = new Vector();
public Customer(String name) {
this.name = name;
}
public void addRental(Rental rental) {
rentals.add(rental);
}
public String getName() {
return name;
}
/**
* 提供一个用于生成详单的函数
*/
public String statement() {
double totalAmount = 0;
//常客计算积分时使用
int frequentRenterPoints = 0;
Enumeration enumeration = rentals.elements();
String result = "Rental Record for " + getName() + "\n";
while (enumeration.hasMoreElements()) {
//总金额
double thisAmount = 0;
Rental each = (Rental) rentals.elements();
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;
}
frequentRenterPoints++;
//如果是新书,另算积分呢
if (each.getMovie().getPriceCode() == Movie.NEW_RELEASE && each.getDaysRented() >= 1) {
frequentRenterPoints++;
}
result += "\t" + each.getMovie().getTitle() + "\t" + String.valueOf(thisAmount) + "\n";
totalAmount += thisAmount;
}
result += "Amount owed is " + String.valueOf(totalAmount) + "\n";
result += "You earned " + String.valueOf(frequentRenterPoints) + " frequent renter points";
return result;
}
}
进行重构
分解statements方法
长长的函数需要大卸八块,代码块越小,代码的移动和处理也就越轻松。将较小代码块移至更合适的类,降低代码重复使新函数更容易撰写。
提取函数
找出逻辑泥团并运用提取函数方法。本例中的switch语句需提炼至独立函数。找出函数内局部变量和参数。each(未被修改,可以当成参数传入新的函数)和thisAmount(会被修改,格外小心,如果只有一个变量修改,可以将其作为返回值)。那么将新函数返回值返回给thisAmount是可行的。
/**
* 提供一个用于生成详单的函数
*/
public String statement() {
....
while (enumeration.hasMoreElements()) {
//总金额
double thisAmount = 0;
Rental each = (Rental) rentals.elements();
thisAmount = amountFor(each);
....
}
....
}
/**
* 金额计算
* @param aRental
* @return
*/
private double amountFor(Rental aRental) {
//注意double、int类型之间的转换。
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类而非Customer类,调整代码以使用新类。
class Rental {
....
/**
* 金额计算
* @return
*/
public double getCharge() {
//注意double、int类型之间的转换。
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;
}
}
public class Customer {
....
/**
* 提供一个用于生成详单的函数
*/
public String statement() {
....
while (enumeration.hasMoreElements()) {
...
result += "\t" + each.getMovie().getTitle() + "\t"
+ String.valueOf(each.getCharge()) + "\n";
totalAmount += each.getCharge();
}
...
}
}
提炼“积分计算”功能
积分计算因影片种类而有所不同,针对“积分计算”代码运用Extract Method重构手法。局部变量each,另一个临时变量是frequentRenterPoints(这个参数在使用之前已初始化,但提炼出的函数并未读取该值,因此无需传入,只需作为新函数的返回值累加上去即可)。
class Rental {
...
/**
* 计算常客积分
* @return
*/
public int getFrequentRenterPoints() {
//如果是新书,另算积分呢
if (getMovie().getPriceCode() == Movie.NEW_RELEASE && getDaysRented() >= 1) {
return 2;
} else {
return 1;
}
}
}
public class Customer {
....
/**
* 提供一个用于生成详单的函数
*/
public String statement() {
double totalAmount = 0;
//常客计算积分时使用
int frequentRenterPoints = 0;
Enumeration enumeration = rentals.elements();
String result = "Rental Record for " + getName() + "\n";
while (enumeration.hasMoreElements()) {
Rental each = (Rental) rentals.elements();
//计算常客积分
frequentRenterPoints += each.getFrequentRenterPoints();
result += "\t" + each.getMovie().getTitle() + "\t" + String.valueOf(each.getCharge()) + "\n";
totalAmount += each.getCharge();
}
result += "Amount owed is " + String.valueOf(totalAmount) + "\n";
result += "You earned " + String.valueOf(frequentRenterPoints) + " frequent renter points";
return result;
}
}
去除临时变量(以查询取代临时变量)
临时变量会造成冗长复杂的函数,以查询取代临时变量方法,以查询函数替代totalAmount和frequentRentalPoints临时变量。任何函数均可调用,促成干净设计、减少冗长函数。
public class Customer {
...
/**
* 提供一个用于生成详单的函数
*/
public String statement() {
//常客计算积分时使用
Enumeration enumeration = rentals.elements();
String result = "Rental Record for " + getName() + "\n";
while (enumeration.hasMoreElements()) {
Rental each = (Rental) rentals.elements();
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;
}
/**
* 获取总积分
* @return
*/
private double getTotalFrequentRenterPoints() {
int result = 0;
Enumeration enumeration = rentals.elements();
while (enumeration.hasMoreElements()) {
Rental each = (Rental) rentals.elements();
result += each.getFrequentRenterPoints();
}
return result;
}
/**
* 获取总金额
* @return
*/
private double getTotalCharge() {
double result = 0;
Enumeration enumeration = rentals.elements();
while (enumeration.hasMoreElements()) {
Rental each = (Rental) rentals.elements();
result += each.getCharge();
}
return result;
}
}
运用多态取代与价格相关的条件逻辑
代码迁移Movie类
用户准备修改影片分类规则。费用计算和常客积分计算也会因此而发生改变。因为系统可能会加入新影片类型,不稳定,因此在Movie对象内计算费用。同样的手法处理常客积分函数。
public class Movie {
...
/**
* 根据影片类型获取费用
* @param daysRented
* @return
*/
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 int getFrequentRenterPoints(int daysRented) {
//如果是新书,另算积分呢
if (getPriceCode() == Movie.NEW_RELEASE && daysRented >= 1) {
return 2;
} else {
return 1;
}
}
}
class Rental {
private Movie movie;
private int daysRented;
...
/**
* 金额计算
* @return
*/
public double getCharge() {
return movie.getCharge(daysRented);
}
/**
* 计算常客积分
* @return
*/
public int getFrequentRenterPoints() {
return movie.getFrequentRenterPoints(daysRented);
}
}
Price类 状态模式
多态取代switch语句,多态设计时不要直接继承Movie,而是通过Price间接去处理,一部影片可以在生命周期内修改自己的分类,但一个对象却不能再生命周期内修改自己所属的类,使用state模式
abstract class Price {
abstract int getPriceCode();
}
public class ChildrenPrice 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;
}
}
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 priceCode) {
this.title = title;
setPriceCode(priceCode);
}
public int getPriceCode() {
return price.getPriceCode();
}
public void setPriceCode(int arg) {
switch (arg) {
case Movie.REGULAR:
price = new RegularPrice();
break;
case Movie.NEW_RELEASE:
price = new NewReleasePrice();
break;
case Movie.CHILDRENS:
price = new ChildrenPrice();
break;
default:
throw new IllegalArgumentException("Incorrect Price Code");
}
}
....
}
}
搬移函数
将Movie中的getCharge方法下沉至Price方法中。
abstract class Price {
abstract int getPriceCode();
/**
* 根据影片类型获取费用
* @param daysRented
* @return
*/
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 {
private Price price;
...
public int getPriceCode() {
return price.getPriceCode();
}
....
}
以多态取代表达式
次取出getPriceCode的一个case分支,在对应的类建立覆盖函数。同样的方法处理getFrequentRenterPoints方法
/**
* 新建Price类,并提供类型相关的行为,为此,加入抽象函数,并在所有子类中加上对应的具体操作。
*/
abstract class Price {
/**
* 获取影片类型code码
* @return
*/
abstract int getPriceCode();
/**
* 根据影片类型获取费用
* @param daysRented
* @return
*/
abstract double getCharge(int daysRented);
/**
* 如果是新书,采用复写的方法,在超类中留下一个已定义的函数,使之成为一种默认行为。
* @param daysRented
* @return
*/
int getFrequentRenterPoints(int daysRented) {
return 1;
}
}
public class RegularPrice extends Price {
@Override
int getPriceCode() {
return Movie.REGULAR;
}
@Override
public 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;
}
@Override
public double getCharge(int daysRented) {
//果然还是新书最贵啊
return daysRented * 3;
}
@Override
public int getFrequentRenterPoints(int daysRented) {
return (daysRented > 1) ? 2 : 1;
}
}
public class ChildrenPrice extends Price{
@Override
int getPriceCode() {
return Movie.CHILDRENS;
}
@Override
public double getCharge(int daysRented) {
double result = 1.5;
if (daysRented > 3) {
result += (daysRented - 3) * 1.5;
}
return result;
}
}
重构之后的类图