重構,第一個案例

1、租賃程序

一個簡單的影片租賃程序,計算每個位顧客的消費金額並打印詳單。node

顧客租了哪些影片、租期多長,程序便更具租賃時間和影片類型算出費用。ios

影片分爲三類:普通片、兒童片和新片。git

除了計算費用,還要爲常客計算積分,積分會根據租片種類是否爲新片而不一樣github

 

程序內容:ide

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類:ui

/* 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程序:this

#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()作的事情太多了,它作了不少應該其餘類作的事情。url

若是須要修改輸出的格式,那就須要再增長一個新的計算函數。

若是須要修改影片的分類方式,它又會影響顧客消費和常客積分。這樣程序又須要更改了。

 

若是發現本身須要爲程序添加一個特性,代碼結構讓你沒法很方便地達成目的,就先重構那個程序,讓代碼更容易添加特性。

重構前,先檢查本身是否有一套可靠的測試機制。這些測試必須能夠自我檢驗。

 

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是不會重載的,子類依然會執行父類的函數,而不是本身的同名函數。

這一點連編譯器都沒有提示。代碼修改連接

相關文章
相關標籤/搜索