1. 前言
重构,可以理解为一种帮助你改进已有代码设计的一种方法。若直接对这种方法下一个定义,那么很容易陷入形式,读完后还是不理解重构是啥。因为它是在你不断设计,不断改进过程中归纳出来的一些比较通用的手法,这些手法会因你遇到的场景及上下文的不同而发生变化,而代码设计过程中的场景是千变万化,异常复杂的。所以,以一个简单案例入手,帮助我们熟悉一般简单场景重构的手法,然后一步步组合变化,去满足我们对大型系统重构的需求是一个很好的思路。
2. 初始需求的描述
该程序用于影片出租店,出租店目前有三种类型的影片出租:普通片、儿童片、新片。需要记录顾客租了哪些影片,租期,并计算租金,统计顾客积分。
2.1 第一版设计
设计三个类:Movie, Rental, Consumer,其中顾客可以查看自己的租金以及积分,在Consumer类增加一个statement接口,用于打印该顾客的租用详单,其设计类图如下:
图一 第一版设计类图
设计代码如下:
#include <iostream>
using namespace std;
enum MovieType {
CHILDRENS, //儿童片
REGULAR, // 普通片
NEW_RELEASE, //新片
}
class Movie
{
public:
MovieType GetMovieType() {
return type;
}
string GetMovieTitle() {
return name;
}
private:
string name;
double price;
MovieType type;
};
class Rental
{
public:
Movie GetMovie() {
return mv;
}
int GetRentDays() {
return days;
}
private:
int days;
Movie mv;
};
class Consumer
{
public:
string statement();
string GetName() {
return name;
}
private:
string name;
vector<Rental> rentals;
};
// 打印详单,包含计算总价格及总积分
string Consumer::statement() {
double totalAmount = 0;
int frequentRenterPoints = 0;
Enumeration rentals = _rentals.elements();
string result = "Rental Record for " + GetName() + "\n";
int i = 0;
while (i < rentals.size()) {
double thisAmount = 0;
//determine amounts for each line
switch (rentals[i].GetMovie().GetMovieType()) {
case REGULAR:
{
thisAmount += 2;
if (rentals[i].GetRentDays() > 2) {
thisAmount += (rentals[i].GetRentDays()- 2)* 1.5;
}
}
break;
case NEW_RELEASE:
{
thisAmount += rentals[i].GetRentDays()* 3;
}
break;
case CHILDRENS:
{
thisAmount += 1.5;
if (rentals[i].GetRentDays() > 3) {
thisAmount += (rentals[i].GetRentDays()- 3)* 1.5;
}
}
break;
default:
break;
}
// add frequent renter points
frequentRenterPoints ++;
// add bonus for a two day new release rental
if ((rentals[i].GetMovie().GetMovieType()== Movie.NEW_RELEASE) &&
rentals[i].GetRentDays() > 1) {
frequentRenterPoints ++;
}
//show figures for this rental
result += "\t" + rentals[i].GetMovie().GetMovieTitle()+ "\t" +
to_string(thisAmount)+ "\n";
totalAmount += thisAmount;
i++;
}
//add footer lines
result += "Amount owed is " + to_string(totalAmount)+ "\n";
result += "You earned " + to_string(frequentRenterPoints)+
" frequent renter points"
return result;
}
int main() {
cout << "test\n";
return 0;
}
分析以上代码,发现可以实现计算租金及积分,并打印出详单的基本功能,但明显存在很多不足:
- statement函数太长,里面做的工作太多
- 若需要以HTML形式打印表单,那么statement接口无法复用
- 可以复制代码重新增加htmlStatement接口,但若计费标准发生变化,需要同时修改两个接口
- 若影片种类增加或计费规则改变,那么需要不断修改两个接口,无法确保两处修改一致
- 难以满足不断变化的客户需求
2.2 第二版设计
首先,针对statement函数过长,功能点集中的缺点进行改善;
第一步: 识别statement函数中较小的代码块