重构,第一个案例
一、租赁程序
一个简单的影片租赁程序,计算每一个位顾客的消费金额并打印详单。
顾客租了哪些影片、租期多长,程序便更具租赁时间和影片类型算出费用。
影片分为三类:普通片、儿童片和新片。
除了计算费用,还要为常客计算积分,积分会根据租片种类是否为新片而不同
程序内容:
movie类:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
/* movie.h */ #ifndef MOVIE_H #define MOVIE_H #include <iostream> #include <string> class Movie { public: Movie(std::string title = "empty", int price = 0); int getPriceCode(); void setPriceCode(int arg); std::string getTitle(); static const int REGULAR; //普通影片 static const int NEW_RELEASE; //新片 static const int CHILDRENS; //儿童影片 private: std::string _title; //影片名 int _priceCode; //价格码 }; #endif // MOVIE_H /* movie.cpp */ #include "movie.h" const int REGULAR = 0; //普通影片 const int NEW_RELEASE = 1; //新片 const int CHILDRENS = 2; //儿童影片 Movie::Movie(std::__cxx11::string title, int price) :_title(title), _priceCode(price) { } int Movie::getPriceCode() { return _priceCode; } void Movie::setPriceCode(int arg) { _priceCode = arg; } std::__cxx11::string Movie::getTitle() { return _title; }
rental类:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
/* rental.h */ #ifndef RENTAL_H #define RENTAL_H #include "movie.h" class Rental { public: Rental(Movie movie, int daysRented); int getDaysRented(); Movie getMovie(); private: Movie _movie; //租赁的影片 int _daysRented; //租期 }; #endif // RENTAL_H /* rental.cpp */ #include "rental.h" Rental::Rental(Movie movie, int daysRented) { _movie = movie; _daysRented = daysRented; } int Rental::getDaysRented() { return _daysRented; } Movie Rental::getMovie() { return _movie; }
customer类:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
/* customer.h */ #ifndef CUSTOMER_H #define CUSTOMER_H #include <string> #include <iostream> #include <vector> #include "movie.h" #include "rental.h" class Customer { public: Customer(std::string name); void addRental(Rental arg); std::string getName(); std::string statement(); std::vector<Rental>& getRentals(); private: std::string _name; //顾客名 std::vector<Rental> _rentals; //租赁列表 }; #endif // CUSTOMER_H /* customer.cpp */ #include "customer.h" Customer::Customer(std::__cxx11::string name) { _name = name; } void Customer::addRental(Rental arg) { _rentals.push_back(arg); } std::__cxx11::string Customer::getName() { return _name; } std::__cxx11::string Customer::statement() { double totalAmount = 0; //总金额 int frequentRenterPoints = 0; //积分点 std::string result = "Rental Record for " + getName() + "\n"; std::vector<Rental>::iterator iter = _rentals.begin(); for(;iter != _rentals.end();++iter) { double thisAmount = 0; //当前单个租赁金额 Rental each = *iter; switch(each.getMovie().getPriceCode()) { case 0: //普通片,起步价为2元,租期超过2天的部分每天1.5元 thisAmount += 2; if(each.getDaysRented() > 2) thisAmount += (each.getDaysRented() - 2) * 1.5; break; case 1: //新片,每天3元 thisAmount += each.getDaysRented() * 3; break; case 2: //儿童片,起步价1.5元,租期超过3天的部分每天1.5元 thisAmount += 1.5; if(each.getDaysRented() > 3) thisAmount += (each.getDaysRented() - 3) * 1.5; break; } frequentRenterPoints++; //每借一张加1个积分点 //积分累加条件:新版本的片子,借的时间大于1天 if((each.getMovie().getPriceCode() == 1) && each.getDaysRented() > 1) { frequentRenterPoints++; } //添加详单 result += "\t" + each.getMovie().getTitle() + "\t" + std::to_string(thisAmount) + "\n"; totalAmount += thisAmount; } //添加脚注 result += "Amount owed is " + std::to_string(totalAmount) + "\n"; result += "You earned " + std::to_string(frequentRenterPoints) + " frequent renter points" +"\n"; return result; } std::vector<Rental> &Customer::getRentals() { return _rentals; }
main程序:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include <iostream> #include <string> #include <vector> #include "movie.h" #include "rental.h" #include "customer.h" using namespace std; int main() { /* create 10 movies */ std::vector<Movie> movies; for(int i=0;i<10;i++) { Movie tempMovie("Movie"+std::to_string(i+1), i+1); movies.push_back(tempMovie); } /* create 5 customers */ std::vector<Customer> customers; for(int i=0;i<5;i++) { Customer tempCustomers("customer" + std::to_string(i+1)); for(int j=2*i;j<2*i+2;++j) { Movie tempMovie = movies[j]; Rental tempRent(tempMovie, i+1); tempCustomers.addRental(tempRent); } customers.push_back(tempCustomers); } //print out all movies information; const std::vector<Movie>::size_type numMovies = movies.size(); for(int i=0;i<numMovies;++i) { Movie tempMovie = movies[i]; std::cout << " the Tile of the "<<i+1 << "(" << tempMovie.getTitle() << "," << tempMovie.getPriceCode() << ")" << std::endl; } std::cout << std::endl; //print out all customers information const std::vector<Customer>::size_type numCustomers = customers.size(); for(int i=0;i<numCustomers;++i) { Customer tempCust = customers[i]; std::cout << "the " << std::to_string(i+1) << " the customer " << tempCust.getName() << " has rented these movies:" << std::endl; const std::vector<Rental>::size_type numRentals = tempCust.getRentals().size(); for(int j=0;j<numRentals;++j) { std::cout << " (" << tempCust.getRentals()[j].getMovie().getTitle() << ", " << tempCust.getRentals()[j].getDaysRented() << ")" << std::endl; } } std::cout << std::endl; for(int i=0;i<numCustomers;++i) { Customer tempCust = customers[i]; std::cout << tempCust.statement() << std::endl; } return 0; }
1.2 程序评价
customer里头的statement()做的事情太多了,它做了很多应该其他类做的事情。
如果需要修改输出的格式,那就需要再增加一个新的计算函数。
如果需要修改影片的分类方式,它又会影响顾客消费和常客积分。这样程序又需要更改了。
如果发现自己需要为程序添加一个特性,代码结构让你无法很方便地达成目的,就先重构那个程序,让代码更容易添加特性。
重构前,先检查自己是否有一套可靠的测试机制。这些测试必须可以自我检验。
1.3 分解和重组statement()
第一步:找出代码的逻辑泥团并运用Extract Method
然后:找出函数内的局部变量和参数
其次:找出其中的被修改的和未被修改的。
未被修改的:用作参数
修改的:用作返回值
重构技术就是以微小步伐修改程序,哪怕出了错误,也能方便的修改
1.4 运用多态取代与加个相关的条件逻辑
switch语句,最好不要在另一个对象的属性基础上运用switch语句。
哪怕不得不使用,也应该在对象自己的数据上使用。而不是在别人的数据上使用。
继承方式提供可修改的分类
步骤一:针对类型代码使用Self Encapsulate Field,确保仍和时候都通过取值函数和设值函数来访问类型代码。
多态中用到的变量用,取值和设值函数替代
新建Price类,提供相关行文,加上子类的对应具体函数
Movie类不再保存_priceCode变量,而用Price对象(state模式)
同样的手法处理getFrequentRenterPoints,但是出错了
1.5 在编写代码时遇到的问题
最后修改getFrequentRenterPoints,多态没有很好的执行。
最后发现CPP基类函数如果没有加virtual是不会重载的,子类依然会执行父类的函数,而不是自己的同名函数。
这一点连编译器都没有提示。代码修改链接