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


    2. 分解并重组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 + "
    ";
            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 += "	" + each.getMovie().getTitle() + "	" +
                     oss.str()+ "
    ";
    
                totalAmount +=thisAmount;
    
                ++iter;
            }
    
            //增加页脚注释
            ostringstream oss;
            oss << totalAmount;
            ret += "Amount owed is " + oss.str() + "
    ";
    
            oss.str("");
            oss << frequentReterPoints;
            ret += "You earned " + oss.str() +"
    ";
            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 + "
    ";
            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 += "	" + each.getMovie().getTitle() + "	" +
                     oss.str()+ "
    ";
    
                totalAmount +=thisAmount;
    
                ++iter;
            }
    
            //增加页脚注释
            ostringstream oss;
            oss << totalAmount;
            ret += "Amount owed is " + oss.str() + "
    ";
    
            oss.str("");
            oss << frequentReterPoints;
            ret += "You earned " + oss.str() +"
    ";
            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 + "
    ";
            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 += "	" + each.getMovie().getTitle() + "	" +
                     oss.str()+ "
    ";
    
                totalAmount +=thisAmount;
    
                ++iter;
            }
    
            //增加页脚注释
            ostringstream oss;
            oss << totalAmount;
            ret += "Amount owed is " + oss.str() + "
    ";
    
            oss.str("");
            oss << frequentReterPoints;
            ret += "You earned " + oss.str() +"
    ";
            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 + "
    ";
    
            vector<Rental*>::iterator iter = rentals.begin();
            while( iter != rentals.end())
            {
                Rental& each = *(*iter);
    
                //显示每个租赁记录
                ostringstream oss;
                oss << each.getCharge();
                ret += "	" + each.getMovie().getTitle() + "	" +
                     oss.str()+ "
    ";
    
                ++iter;
            }
    
            //增加页脚注释
            ostringstream oss;
            oss << getTotalCharge(); //用getTotalCharge代替totalAmount
            ret += "Amount owed is " + oss.str() + "
    ";
    
            oss.str("");
            oss << getTotalFrequentRenterPointers();
            ret += "You earned " + oss.str() +"
    ";
            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.5
    Amount owed is 15.5
    You earned 4
    */
  • 相关阅读:
    ICommand接口
    Binding自动侦听
    WPF比较两个随机数大小写,利用MVVM思想实现
    从一个ListBox中的元素点击导入另一个ListBox元素中
    利用FluidMoveBehavior制作出手机通讯录平滑的效果
    从零开始学习Gradle之三---多项目构建
    用Gradle 构建你的android程序
    iPhone/iPad调整事件递交
    iOS8-Sampler
    iOS8-Sampler
  • 原文地址:https://www.cnblogs.com/5iedu/p/5918002.html
Copyright © 2020-2023  润新知