首页 > 代码库 > 第1章 重构,第一个案例(2):分解并重组statement函数

第1章 重构,第一个案例(2):分解并重组statement函数

2. 重构的第一步:建立一组可靠的测试环境

3. 分解并重组statement

(1)提炼switch语句到独立函数(amountFor)和注意事项。

  ①先找出函数内的局部变量和参数:each和thisAmount,前者在switch语句内未被修改,后者会被修改。

  ②任何不会被修改的变量都可以当成参数传入新的函数,如将each为作为参数传给amountFor()的形参

  ③至于会被修改的变量要格外小心,如果只有一个变量会被修改(如thisAmount),则可以当作新函数的返回值

【实例分析】影片出租2

技术分享
//顾客类(用来表示顾客)class Customer{private:    string name; //顾客姓名    vector<Rental*> rentals; //每个租赁记录public:    Customer(string name)    {        this->name = name;    }    void addRental(Rental* value)    {        rentals.push_back(value);    }    string getName(){return name;}    //statement(报表),生成租赁的详单    string statement()    {        string ret = "Rental Record for " + name + "\n";        double totalAmount = 0;  //总租金额        int frequentReterPoints = 0; //常客积分        vector<Rental*>::iterator iter = rentals.begin();        while( iter != rentals.end())        {            double thisAmount = 0;   //每片需要的租金            Rental& each = *(*iter);            //each在switch不会变化,做为参数转给amountFor,而thisAmount作为返回值            thisAmount = amountFor(each);            //常客积分            ++frequentReterPoints;            //如果是新片且租期超过1天以上,则额外送1分积分            if ((each.getMovie().getPriceCode() == Movie::NEW_RELEASE) &&                each.getDaysRented() > 1)  ++frequentReterPoints;            //显示每个租赁记录            ostringstream oss;            oss << thisAmount;            ret += "\t" + each.getMovie().getTitle() + "\t" +                 oss.str()+ "\n";            totalAmount +=thisAmount;            ++iter;        }        //增加页脚注释        ostringstream oss;        oss << totalAmount;        ret += "Amount owed is " + oss.str() + "\n";        oss.str("");        oss << frequentReterPoints;        ret += "You earned " + oss.str() +"\n";        return ret;    }    //计算金额switch代码从statement中分离出来    //但这个函数存在一个问题:该函数是Customer类的成员函数,但没有用于Customer    //类中的任何信息,却使用了Rental类。因此可以将这段代码转移到Rental类中    double amountFor(Rental& aRental) //参数相当于原statement中的each    {        double result = 0 ;//相当于statement中的thisamount;        switch(aRental.getMovie().getPriceCode())        {        case Movie::REGULAR:            result += 2;    //普通片基本租金为2元            if(aRental.getDaysRented() > 2)  //超过2天的每天加1.5元                result +=(aRental.getDaysRented() - 2 ) * 1.5;            break;        case Movie::NEW_RELEASE:            result += aRental.getDaysRented() * 3;    //新片的租金            break;        case Movie::CHILDRENS:            result += 1.5;    //儿童片基本租金为1.5元            if(aRental.getDaysRented() > 3)  //超过3天的每天加1.5元                result +=(aRental.getDaysRented() - 3 ) * 1.5;            break;        }       return result;    }};
View Code

(2)搬移“金额计算”代码

技术分享 

  ①上述amountFor函数使用Rental类的信息,却没有使用来自Customer类的信息。

  ②因此可以将这段代码从Customer类中转移到Rental类中去。因为绝大多数情况下,函数应该放在它所使用的数据所属的对象内

  ③搬移到Rental类时将函数名改为getCharge,并去掉部分参数。

  ④删除Customer中的amountFor函数,并找到类中对该函数的引用点,同时修改相应的代码。本例中只有一处,即thisAmount = amountFor(each)改为thisAmount = each.getCharge();

  ⑤如果amountFor中Customer中的一个public函数,这里也可以保留这个函数,让这个旧函数去调用getCharge(),以防止接口变化对其他类产生的影响。

  ⑥去除thisAmoun临时变量,因为thisAmount接受了each.getCharge()后并不再变化,可以原来使用thisAmount的地方直接用each.getCharge()替代,并这也需要付出性能上的代价。

【实例分析】影片出租3

技术分享
//租赁类(表示某个顾客租了一部影片)class Rental{private:    Movie& movie;   //所租的影片    int daysRented; //租期public:    Rental(Movie& movie, int daysRented):movie(movie)    {        this->daysRented = daysRented;    }    int getDaysRented(){return daysRented;}    Movie& getMovie()    {        return movie;    }    //将原Customer类中的amountFor搬到Rental类中,然后重命名为getCharge函数    //同时去掉原来的形参    double getCharge()    {        double result = 0 ;//相当于statement中的thisamount;        switch(getMovie().getPriceCode())        {        case Movie::REGULAR:            result += 2;    //普通片基本租金为2元            if(getDaysRented() > 2)  //超过2天的每天加1.5元                result +=(getDaysRented() - 2 ) * 1.5;            break;        case Movie::NEW_RELEASE:            result += getDaysRented() * 3;    //新片的租金            break;        case Movie::CHILDRENS:            result += 1.5;    //儿童片基本租金为1.5元            if(getDaysRented() > 3)  //超过3天的每天加1.5元                result +=(getDaysRented() - 3 ) * 1.5;            break;        }       return result;    }};//顾客类(用来表示顾客)class Customer{private:    string name; //顾客姓名    vector<Rental*> rentals; //每个租赁记录public:    Customer(string name)    {        this->name = name;    }    void addRental(Rental* value)    {        rentals.push_back(value);    }    string getName(){return name;}    //statement(报表),生成租赁的详单    string statement()    {        string ret = "Rental Record for " + name + "\n";        double totalAmount = 0;  //总租金额        int frequentReterPoints = 0; //常客积分        vector<Rental*>::iterator iter = rentals.begin();        while( iter != rentals.end())        {            double thisAmount = 0;   //每片需要的租金            Rental& each = *(*iter);            //将原来所有对amountFor的引用改为each.getCharge();            thisAmount = each.getCharge(); //原书中thisAmount临时变量也去除,当使用使用到                                           //thisAmount时,直接用each.getCharge()替换。            //常客积分            ++frequentReterPoints;            //如果是新片且租期超过1天以上,则额外送1分积分            if ((each.getMovie().getPriceCode() == Movie::NEW_RELEASE) &&                each.getDaysRented() > 1)  ++frequentReterPoints;            //显示每个租赁记录            ostringstream oss;            oss << thisAmount;            ret += "\t" + each.getMovie().getTitle() + "\t" +                 oss.str()+ "\n";            totalAmount +=thisAmount;            ++iter;        }        //增加页脚注释        ostringstream oss;        oss << totalAmount;        ret += "Amount owed is " + oss.str() + "\n";        oss.str("");        oss << frequentReterPoints;        ret += "You earned " + oss.str() +"\n";        return ret;    }    //将原来的AmountFor函数搬移到Rental类中,并AmountFor函数};
View Code

(3)提炼“常客积分计算”代码

  ①“常客积分”计算规则是视影片种类而不同的,所以可以计算积分的责任放在Rental类中,因为Rental类中保存有计算“常客积分”所需的所有数据。

  ②局部变量each在原代码中没变化,可以作为新函数的参数传入,而frequentRenterPoints会变化,则作为函数的返回值。

【实例分析】影片出租4

技术分享 

技术分享
//租赁类(表示某个顾客租了一部影片)class Rental{private:    Movie& movie;   //所租的影片    int daysRented; //租期public:    Rental(Movie& movie, int daysRented):movie(movie)    {        this->daysRented = daysRented;    }    int getDaysRented(){return daysRented;}    Movie& getMovie()    {        return movie;    }    //将原Customer类中的amountFor搬到Rental类中,然后重命名为getCharge函数    //同时去掉原来的形参    double getCharge()    {        double result = 0 ;//相当于statement中的thisamount;        switch(getMovie().getPriceCode())        {        case Movie::REGULAR:            result += 2;    //普通片基本租金为2元            if(getDaysRented() > 2)  //超过2天的每天加1.5元                result +=(getDaysRented() - 2 ) * 1.5;            break;        case Movie::NEW_RELEASE:            result += getDaysRented() * 3;    //新片的租金            break;        case Movie::CHILDRENS:            result += 1.5;    //儿童片基本租金为1.5元            if(getDaysRented() > 3)  //超过3天的每天加1.5元                result +=(getDaysRented() - 3 ) * 1.5;            break;        }       return result;    }    //将原Customer类的statement中计算常客积分的代码移到Rental类    int getFrequentRenterPoints()    {        //如果是新片且租期超过1天以上,则额外送1分积分        if ((getMovie().getPriceCode() == Movie::NEW_RELEASE) &&            getDaysRented() > 1)  return 2;        else  return 1;    }};//顾客类(用来表示顾客)class Customer{private:    string name; //顾客姓名    vector<Rental*> rentals; //每个租赁记录public:    Customer(string name)    {        this->name = name;    }    void addRental(Rental* value)    {        rentals.push_back(value);    }    string getName(){return name;}    //statement(报表),生成租赁的详单    string statement()    {        string ret = "Rental Record for " + name + "\n";        double totalAmount = 0;  //总租金额        int frequentReterPoints = 0; //常客积分        vector<Rental*>::iterator iter = rentals.begin();        while( iter != rentals.end())        {            double thisAmount = 0;   //每片需要的租金            Rental& each = *(*iter);            //将原来所有对amountFor的引用改为each.getCharge();            thisAmount = each.getCharge(); //原书中thisAmount临时变量也去除,当使用使用到                                           //thisAmount时,直接用each.getCharge()替换。            //提炼“常客积分”后这里的代码            frequentReterPoints += each.getFrequentRenterPoints();            //显示每个租赁记录            ostringstream oss;            oss << thisAmount;            ret += "\t" + each.getMovie().getTitle() + "\t" +                 oss.str()+ "\n";            totalAmount +=thisAmount;            ++iter;        }        //增加页脚注释        ostringstream oss;        oss << totalAmount;        ret += "Amount owed is " + oss.str() + "\n";        oss.str("");        oss << frequentReterPoints;        ret += "You earned " + oss.str() +"\n";        return ret;    }    //将原来的AmountFor函数搬移到Rental类中,并AmountFor函数};
View Code

(4)去除临时变量

  ①Customer类的statement中有两个临时变量:totalAmount和frequentRenterPoints变量。这些临时变量会使函数变得更加冗长和复杂

  ②这两个临时变量主要用来从Rental对象中获得某个总量。可以利用getTotalChargegetTotalFrequentRenterPoints函数来取代,从而使用代码更干净,减少函数的冗长和复杂(当然由于是函数调用,也损失了性能)。

【实例分析】影片出租5

技术分享

//第1章:重构,第1个案例//场景:影片出租,计算每一位顾客的消费金额/*说明:1. 影片分3类:普通片、儿童片和新片。2. 每种影片计算租金的方式。   A.普通片:基本租金为2元,超过2天的部分每天加1.5元   B.新片:租期*3   C.儿童片:基本租金为1.5元,超过3天的部分每天加1.5元3. 积分的计算:每借1片,积分加1,如果是新片且租期1天以上的额外赠送1分。*/#include <iostream>#include <vector>#include <string>#include <sstream>using namespace std;//影片类(只是一个简单的纯数据类)class Movie{private:    string title; //片名    int pricecode; //价格public:    static const int CHILDRENS = 2; //儿童片    static const int REGULAR = 0;   //普通片    static const int NEW_RELEASE = 1;//新片    Movie(string title, int priceCode)    {        this->title = title;        this->pricecode = priceCode;    }    string getTitle(){return title;}    void setTitle(string value)    {        title = value;    }    int getPriceCode(){return pricecode;}    void setPriceCode(int value)    {        this->pricecode = value;    }};//租赁类(表示某个顾客租了一部影片)class Rental{private:    Movie& movie;   //所租的影片    int daysRented; //租期public:    Rental(Movie& movie, int daysRented):movie(movie)    {        this->daysRented = daysRented;    }    int getDaysRented(){return daysRented;}    Movie& getMovie()    {        return movie;    }    //将原Customer类中的amountFor搬到Rental类中,然后重命名为getCharge函数    //同时去掉原来的形参    double getCharge()    {        double result = 0 ;//相当于statement中的thisamount;        switch(getMovie().getPriceCode())        {        case Movie::REGULAR:            result += 2;    //普通片基本租金为2元            if(getDaysRented() > 2)  //超过2天的每天加1.5元                result +=(getDaysRented() - 2 ) * 1.5;            break;        case Movie::NEW_RELEASE:            result += getDaysRented() * 3;    //新片的租金            break;        case Movie::CHILDRENS:            result += 1.5;    //儿童片基本租金为1.5元            if(getDaysRented() > 3)  //超过3天的每天加1.5元                result +=(getDaysRented() - 3 ) * 1.5;            break;        }       return result;    }    //将原Customer类的statement中计算常客积分的代码移到Rental类    int getFrequentRenterPoints()    {        //如果是新片且租期超过1天以上,则额外送1分积分        if ((getMovie().getPriceCode() == Movie::NEW_RELEASE) &&            getDaysRented() > 1)  return 2;        else  return 1;    }};//顾客类(用来表示顾客)class Customer{private:    string name; //顾客姓名    vector<Rental*> rentals; //每个租赁记录     //获得总消费     double getTotalCharge()     {        double result = 0;        vector<Rental*>::iterator iter = rentals.begin();        while( iter != rentals.end())        {            Rental& each = *(*iter);            result += each.getCharge();            ++iter;        }        return result;     }     //获得总积分    int getTotalFrequentRenterPointers()    {        int result = 0;        vector<Rental*>::iterator iter = rentals.begin();        while( iter != rentals.end())        {            Rental& each = *(*iter);            result += each.getFrequentRenterPoints();            ++iter;         }         return result;     }public:    Customer(string name)    {        this->name = name;    }    void addRental(Rental* value)    {        rentals.push_back(value);    }    string getName(){return name;}    //statement(报表),生成租赁的详单    string statement()    {        string ret = "Rental Record for " + name + "\n";        vector<Rental*>::iterator iter = rentals.begin();        while( iter != rentals.end())        {            Rental& each = *(*iter);            //显示每个租赁记录            ostringstream oss;            oss << each.getCharge();            ret += "\t" + each.getMovie().getTitle() + "\t" +                 oss.str()+ "\n";            ++iter;        }        //增加页脚注释        ostringstream oss;        oss << getTotalCharge(); //用getTotalCharge代替totalAmount        ret += "Amount owed is " + oss.str() + "\n";        oss.str("");        oss << getTotalFrequentRenterPointers();        ret += "You earned " + oss.str() +"\n";        return ret;    }};void init(Customer& customer){    Movie* mv = new Movie("倚天屠龙记",Movie::REGULAR);    Rental* rt = new Rental(*mv, 2);    customer.addRental(rt);    mv = new Movie("新水浒传",Movie::NEW_RELEASE);    rt = new Rental(*mv, 3);    customer.addRental(rt);    mv = new Movie("喜羊羊与灰太狼",Movie::CHILDRENS);    rt = new Rental(*mv, 5);    customer.addRental(rt);}int main(){    Customer customer("SantaClaus");    init(customer);    cout << customer.statement() <<endl;    return 0;}/*输出结果Rental Record for SantaClaus        倚天屠龙记      2        新水浒传        9        喜羊羊与灰太狼  4.5Amount owed is 15.5You earned 4*/

 

第1章 重构,第一个案例(2):分解并重组statement函数