重构,第一个案例

时间:2022-12-01 17:04:12

一、租赁程序

一个简单的影片租赁程序,计算每一个位顾客的消费金额并打印详单。

顾客租了哪些影片、租期多长,程序便更具租赁时间和影片类型算出费用。

影片分为三类:普通片、儿童片和新片。

除了计算费用,还要为常客计算积分,积分会根据租片种类是否为新片而不同

 

程序内容:

movie类:

重构,第一个案例重构,第一个案例
/* 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;
}
movie类

rental类:

重构,第一个案例重构,第一个案例
/* 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;
}
Rental类

customer类:

重构,第一个案例重构,第一个案例
/* 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;
}
Customer类

main程序:

重构,第一个案例重构,第一个案例
#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;
}
main程序

 

1.2 程序评价

customer里头的statement()做的事情太多了,它做了很多应该其他类做的事情。

如果需要修改输出的格式,那就需要再增加一个新的计算函数。

如果需要修改影片的分类方式,它又会影响顾客消费和常客积分。这样程序又需要更改了。

 

如果发现自己需要为程序添加一个特性,代码结构让你无法很方便地达成目的,就先重构那个程序,让代码更容易添加特性。

重构前,先检查自己是否有一套可靠的测试机制。这些测试必须可以自我检验。

 

1.3 分解和重组statement()

第一步:找出代码的逻辑泥团并运用Extract Method

然后:找出函数内的局部变量和参数

其次:找出其中的被修改的和未被修改的。

未被修改的:用作参数

修改的:用作返回值

 

把switch提取出来作为函数

重构技术就是以微小步伐修改程序,哪怕出了错误,也能方便的修改

提炼常客积分代码

去除thisAmount临时变量

去除totalAmount临时变量

 

 

 

1.4 运用多态取代与加个相关的条件逻辑

switch语句,最好不要在另一个对象的属性基础上运用switch语句。

哪怕不得不使用,也应该在对象自己的数据上使用。而不是在别人的数据上使用。

将getCharge()移动到Movie类中去

同样的方法修改常客积分

 

继承方式提供可修改的分类

步骤一:针对类型代码使用Self Encapsulate Field,确保仍和时候都通过取值函数和设值函数来访问类型代码。

多态中用到的变量用,取值和设值函数替代

新建Price类,提供相关行文,加上子类的对应具体函数

Movie类不再保存_priceCode变量,而用Price对象(state模式)

新建Price类,提供相关行文,加上子类的对应具体函数

同样的手法处理getFrequentRenterPoints,但是出错了 

 

1.5 在编写代码时遇到的问题

最后修改getFrequentRenterPoints,多态没有很好的执行。

最后发现CPP基类函数如果没有加virtual是不会重载的,子类依然会执行父类的函数,而不是自己的同名函数。

这一点连编译器都没有提示。代码修改链接