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 + "\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; } };
(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函数 };
(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函数 };
(4)去除临时变量
①Customer类的statement中有两个临时变量:totalAmount和frequentRenterPoints变量。这些临时变量会使函数变得更加冗长和复杂。
②这两个临时变量主要用来从Rental对象中获得某个总量。可以利用getTotalCharge和getTotalFrequentRenterPoints函数来取代,从而使用代码更干净,减少函数的冗长和复杂(当然由于是函数调用,也损失了性能)。
【实例分析】影片出租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.5 Amount owed is 15.5 You earned 4 */