首页 > 代码库 > 第1章 重构,第一个案例(3):运用多态取代switch
第1章 重构,第一个案例(3):运用多态取代switch
3. 运用多态取代与价格相关的条件逻辑
3.1 switch和“常客积分”代码的再次搬迁
(1)switch:最好不要在另一个对象的属性上运用switch语句
switch(getMovie().getPriceCode()) //在movie对象的priceCode属性上运用switch{ //这意味着可以将getCharge函数从Rental类移动到Movie类去 //选择在Movie类中封装计算费用功能,还有一个 //原因,就是可以控制因影片类型变化导致的计算 //方式变化,从而对其它对象产生影响。 …
}
(2)常客积分:getFrequentRenterPoints函数的再次搬迁。用跟处理getCharge相同的手法处理常客积分,将因影片类型变化而变化的所有东西都放到Movie类中去处理。Rental类中只需调用Movie相应的方法即可。
【实例分析】影片出租1.3.1
//第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: enum MovieType{ REGULAR = 0, //普通片 NEW_RELEASE, //新片 CHILDRENS //儿童片 }; 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; } //将原来Rental类中的getCharge移到该类中,并将租期作为参数传入 //搬到这里来的原因是 //1.switch语句中getPriceCode为本类对象的属性 //2.封装影片类型的变化导致计算方式变化于该类中,从而降低对其他类的影响 double getCharge(int daysRented) { double result = 0 ;//相当于statement中的thisamount; switch(getPriceCode()) { case Movie::REGULAR: result += 2; //普通片基本租金为2元 if(daysRented > 2) //超过2天的每天加1.5元 result +=(daysRented - 2 ) * 1.5; break; case Movie::NEW_RELEASE: result += daysRented * 3; //新片的租金 break; case Movie::CHILDRENS: result += 1.5; //儿童片基本租金为1.5元 if(daysRented > 3) //超过3天的每天加1.5元 result +=(daysRented - 3 ) * 1.5; break; } return result; } //将原Rental类中常客积分搬到该类中 //原因是常客积分的计费方式与影片类型有关,也是为了控制当 //影片类型变化时,由于计算方式变化对其他类的影响 int getFrequentRenterPoints(int daysRented) { //如果是新片且租期超过1天以上,则额外送1分积分 if ((getPriceCode() == Movie::NEW_RELEASE) && daysRented > 1 ) return 2; else return 1; }};//租赁类(表示某个顾客租了一部影片)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; } double getCharge() { return movie.getCharge(daysRented); } //将原Customer类的statement中计算常客积分的代码移到Rental类 int getFrequentRenterPoints() { return movie.getFrequentRenterPoints(daysRented); }};//顾客类(用来表示顾客)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; } void cleanUp() { vector<Rental*>::iterator iter = rentals.begin(); while( iter != rentals.end()) { delete(*iter); ++iter; } rentals.clear(); } template <typename T> string numToString(T num) { stringstream ss; ss << num; return ss.str(); }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); //显示每个租赁记录 ret += "\t" + each.getMovie().getTitle() + "\t" + numToString(each.getCharge())+ "\n"; ++iter; } //增加页脚注释 ret += "Amount owed is " + numToString(getTotalCharge()) + "\n";//用getTotalCharge代替totalAmount ret += "You earned " + numToString(getTotalFrequentRenterPointers()) +"\n"; return ret; } ~Customer() { cleanUp(); }};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*/
3.2 运用子类取代类型码
(1)使用继承子类的方式,可以利用多态来取代switch语句。
(2)重构的步骤
①使用“自我封装字段”的方法将类型码通过get/set函数封装起来(如Movie类的getPriceCode函数)。如果类型码被传递给构造函数,就需要将构造函数换成工厂方法(如createMovie函数)
②以类型码的宿主类为基类,为类型码的每一个数值建立一个相应的子类。在每个子类中覆写类型码的取值函数,使其返回相应的类型码值。(见Movie子类getPriceCode)
③从父类中删除保存类型码的字段(即旧Movie类的priceCode字段),将类型码访问函数声明为抽象函数(如Movie中的getPriceCode)
④使用pushDownMethod/Field方法将与特定类型码相关的函数推到子类来实现(如本例中的getCharge函数)
(3)缺点:
①对于某个具体对象,在其生命周期中其状态(或本例中类型码)是不可改变的(如代表Movie子类型的priceCode是不能更改的),所以当创建了一部影片出来以后,就不能修改其类型了。如,现在某影片是“新片”类型,但即使随着时间的推移,也不能更改为“普通片”或“儿童片”了。
②我们总是在避免使用switch语句,虽然利用了多态将各个case语句的代码分解到相应的子类中去实现。但在Movie类的createMovie函数中仍然要出现switch语句。幸运的是,仅此一处用到switch,并且只用于决定创建何种对象而没有其他的业务逻辑,所以这样的switch语句是可以接受的。
【实例分析】影片出租1.3.2
//types.h
#include <vector>#include <string>#include <sstream>using namespace std;//影片类class Movie{private: string title; //片名 //int priceCode; //价格,注意,这里被注释了public: enum MovieType{ REGULAR = 0, //普通片 NEW_RELEASE, //新片 CHILDRENS //儿童片 }; Movie(string title); static Movie* createMovie(string title, int priceCode); string getTitle(); void setTitle(string value); //类型码的“自我封装”(提供取值函数) virtual int getPriceCode() = 0; //将原来Rental类中的getCharge移到该类中,并将租期作为参数传入 //搬到这里来的原因是 //1.switch语句中getPriceCode为本类对象的属性 //2.封装影片类型的变化导致计算方式变化于该类中,从而降低对其他类的影响 virtual double getCharge(int daysRented); int getFrequentRenterPoints(int daysRented);};//普通片:用子类取代类型码class RegularMovie: public Movie{public: RegularMovie(string title); int getPriceCode(); double getCharge(int daysRented);};//儿童片:class ChildrensMovie: public Movie{public: ChildrensMovie(string title); int getPriceCode(); double getCharge(int daysRented);};//新片class ReleaseMovie: public Movie{public: ReleaseMovie(string title); int getPriceCode(); double getCharge(int daysRented);};//租赁类(表示某个顾客租了一部影片)class Rental{private: Movie& movie; //所租的影片 int daysRented; //租期public: Rental(Movie& movie, int daysRented); int getDaysRented(); Movie& getMovie(); double getCharge(); //将原Customer类的statement中计算常客积分的代码移到Rental类 int getFrequentRenterPoints();};//顾客类(用来表示顾客)class Customer{private: string name; //顾客姓名 vector<Rental*> rentals; //每个租赁记录 //获得总消费 double getTotalCharge(); //获得总积分 int getTotalFrequentRenterPointers(); void cleanUp(); template <typename T> string numToString(T num) { stringstream ss; ss << num; return ss.str(); }public: Customer(string name); void addRental(Rental* value); string getName(); //statement(报表),生成租赁的详单 string statement(); ~Customer();};
//main.cpp
//第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 "types.h"using namespace std;//*********************************************影片类*************************************Movie::Movie(string title){ this->title = title;}//提供创建子类实例的静态函数(也可以使用工厂方法)Movie* Movie::createMovie(string title, int priceCode){ Movie* ret = NULL; //利用子类替代switch的分支。我们总是在避免使用switch语句。但这里 //只有一处用到switch,并且只用于决定创建何种对象而没有其他的业务逻辑 //所以这样的switch语句是可以接受的。 switch(priceCode) { case Movie::REGULAR: ret = new RegularMovie(title); break; case Movie::CHILDRENS: ret = new ChildrensMovie(title); break; case Movie::NEW_RELEASE: ret = new ReleaseMovie(title); break; } return ret;}string Movie::getTitle(){ return title;}void Movie::setTitle(string value){ title = value;}double Movie::getCharge(int daysRented){ return Movie::REGULAR;}//将原Rental类中常客积分搬到该类中//原因是常客积分的计费方式与影片类型有关,也是为了控制当//影片类型变化时,由于计算方式变化对其他类的影响int Movie::getFrequentRenterPoints(int daysRented){ //如果是新片且租期超过1天以上,则额外送1分积分 if ((getPriceCode() == Movie::NEW_RELEASE) && daysRented > 1 ) return 2; else return 1;}//普通片:用子类取代类型码RegularMovie::RegularMovie(string title):Movie(title){}int RegularMovie::getPriceCode(){ return Movie::REGULAR;}//使用pushDownMethod(Field)方法将与特定类型码相关的函数推到子类来实现double RegularMovie::getCharge(int daysRented){ double result = 2 ; if(daysRented > 2) //超过2天的每天加1.5元 result +=(daysRented - 2 ) * 1.5; return result;}//儿童片ChildrensMovie::ChildrensMovie(string title):Movie(title){}int ChildrensMovie::getPriceCode(){ return Movie::CHILDRENS;}//使用pushDownMethod(Field)方法将与特定类型码相关的函数推到子类来实现double ChildrensMovie::getCharge(int daysRented){ double result = 1.5;//儿童片基本租金为1.5元 if(daysRented > 3) //超过3天的每天加1.5元 result +=(daysRented - 3 ) * 1.5; return result;}//新片ReleaseMovie::ReleaseMovie(string title):Movie(title){}int ReleaseMovie::getPriceCode(){ return Movie::NEW_RELEASE;}//使用pushDownMethod(Field)方法将与特定类型码相关的函数推到子类来实现double ReleaseMovie::getCharge(int daysRented){ return daysRented * 3; //新片的租金}//********************************租赁类(表示某个顾客租了一部影片)**********************************Rental::Rental(Movie& movie, int daysRented):movie(movie){ this->daysRented = daysRented;}int Rental::getDaysRented(){return daysRented;}Movie& Rental::getMovie(){ return movie;}double Rental::getCharge(){ return movie.getCharge(daysRented);}//将原Customer类的statement中计算常客积分的代码移到Rental类int Rental::getFrequentRenterPoints(){ return movie.getFrequentRenterPoints(daysRented);}//*********************************顾客类(用来表示顾客)*************************************//获得总消费double Customer::getTotalCharge(){ double result = 0; vector<Rental*>::iterator iter = rentals.begin(); while( iter != rentals.end()) { Rental& each = *(*iter); result += each.getCharge(); ++iter; } return result;} //获得总积分int Customer::getTotalFrequentRenterPointers(){ int result = 0; vector<Rental*>::iterator iter = rentals.begin(); while( iter != rentals.end()) { Rental& each = *(*iter); result += each.getFrequentRenterPoints(); ++iter; } return result;}void Customer::cleanUp(){ vector<Rental*>::iterator iter = rentals.begin(); while( iter != rentals.end()) { delete(*iter); ++iter; } rentals.clear();}Customer::Customer(string name){ this->name = name;}void Customer::addRental(Rental* value){ rentals.push_back(value);}string Customer::getName(){return name;}//statement(报表),生成租赁的详单string Customer::statement(){ string ret = "Rental Record for " + name + "\n"; vector<Rental*>::iterator iter = rentals.begin(); while( iter != rentals.end()) { Rental& each = *(*iter); //显示每个租赁记录 ret += "\t" + each.getMovie().getTitle() + "\t" + numToString(each.getCharge())+ "\n"; ++iter; } //增加页脚注释 ret += "Amount owed is " + numToString(getTotalCharge()) + "\n";//用getTotalCharge代替totalAmount ret += "You earned " + numToString(getTotalFrequentRenterPointers()) +"\n"; return ret;}Customer::~Customer(){ cleanUp();}//************************************************初始化数据********************************************void init(Customer& customer){ Movie* mv = Movie::createMovie("倚天屠龙记",Movie::REGULAR); Rental* rt = new Rental(*mv, 2); customer.addRental(rt); mv = Movie::createMovie("新水浒传",Movie::NEW_RELEASE); rt = new Rental(*mv, 3); customer.addRental(rt); mv = Movie::createMovie("喜羊羊与灰太狼",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*/
3.3 以state/strategy取代类型码
(1)对象的状态在生命周期内可以变化,可以选用state模式(本例中有3种状态:REGULAR、CHILDREN、NEW_RELEASE)。而前一个例子中,对象与其状态是紧耦合的,本例利用state模式来组合对象与其状态,达到松耦合的目的。
(2)重构的步骤
①使用SelfEncapsulate Field来封装类型码,确保任何时候都通过取值和设值函数访问类型代码。
②新建一个Price类,并在其中提供类型相关的函数。如Price类中加入一个纯虚函数getPriceCode,并在所有子类中加上对应的具体函数。
③为Price添加子类,每个子类对应一种类型码。并提供getPriceCode的具体实现。
④利用pushDownMethod/Field方法将与类型码相关的函数从Price类推到子类去实现。(如getCharge、getFrequentRenterPoints函数)
⑤在Movie类中保存一个Price的引用,用来保存新建的状态对象。同时调整Movie中各个与类型码相关的函数,将动作转发到状态对象上(如Movie的getCharge函数)。
(3)引入state模式的好处
①如果要修改任何与价格有关的行为只需修改相应的子类即可。添加新的定价标准也只需扩展新的Price子类即可。
②修改影片分类结构或是改变费用的计算规则、改变常客积分计算规则都很容易。
【实例分析】影片出租1.3.3
//types.h
#include <vector>#include <string>#include <sstream>using namespace std;enum MovieType{ REGULAR = 0, //普通片 NEW_RELEASE, //新片 CHILDRENS //儿童片};//price类class Price{public: virtual int getPriceCode() = 0; virtual double getCharge(int daysRented) = 0; virtual int getFrequentRenterPoints(int daysRented){return 1;} virtual ~Price(){}};//普通:class RegularPrice: public Price{public: int getPriceCode(); double getCharge(int daysRented);};//儿童片:class ChildrensPrice: public Price{public: int getPriceCode(); double getCharge(int daysRented);};//新片class ReleasePrice: public Price{public: int getPriceCode(); double getCharge(int daysRented); int getFrequentRenterPoints(int daysRented);};//影片类class Movie{private: string title; //片名 Price* price; //价格类public: Movie(string title, int priceCode); ~Movie(); string getTitle(); void setTitle(string value); int getPriceCode(); void setPriceCode(int priceCode); double getCharge(int daysRented); int getFrequentRenterPoints(int daysRented);};//租赁类(表示某个顾客租了一部影片)class Rental{private: Movie& movie; //所租的影片 int daysRented; //租期public: Rental(Movie& movie, int daysRented); int getDaysRented(); Movie& getMovie(); double getCharge(); //将原Customer类的statement中计算常客积分的代码移到Rental类 int getFrequentRenterPoints();};//顾客类(用来表示顾客)class Customer{private: string name; //顾客姓名 vector<Rental*> rentals; //每个租赁记录 //获得总消费 double getTotalCharge(); //获得总积分 int getTotalFrequentRenterPointers(); void cleanUp(); template <typename T> string numToString(T num) { stringstream ss; ss << num; return ss.str(); }public: Customer(string name); void addRental(Rental* value); string getName(); //statement(报表),生成租赁的详单 string statement(); ~Customer();};
//main.cpp
//第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 "types.h"using namespace std;//*******************************************价格类**************************************//普通片:用子类取代类型码int RegularPrice::getPriceCode(){ return REGULAR;}//使用pushDownMethod(Field)方法将与特定类型码相关的函数推到子类来实现double RegularPrice::getCharge(int daysRented){ double result = 2 ; if(daysRented > 2) //超过2天的每天加1.5元 result +=(daysRented - 2 ) * 1.5; return result;}//儿童片int ChildrensPrice::getPriceCode(){ return CHILDRENS;}//使用pushDownMethod(Field)方法将与特定类型码相关的函数推到子类来实现double ChildrensPrice::getCharge(int daysRented){ double result = 1.5;//儿童片基本租金为1.5元 if(daysRented > 3) //超过3天的每天加1.5元 result +=(daysRented - 3 ) * 1.5; return result;}//新片int ReleasePrice::getPriceCode(){ return NEW_RELEASE;}//使用pushDownMethod(Field)方法将与特定类型码相关的函数推到子类来实现double ReleasePrice::getCharge(int daysRented){ return daysRented * 3; //新片的租金}int ReleasePrice::getFrequentRenterPoints(int daysRented){ return (daysRented > 1) ? 2: 1;}//*********************************************影片类*************************************Movie::Movie(string title, int priceCode):price(NULL){ this->title = title; setPriceCode(priceCode);}string Movie::getTitle(){ return title;}void Movie::setTitle(string value){ title = value;}int Movie::getPriceCode(){ return price->getPriceCode();}void Movie::setPriceCode(int priceCode){ if (price != NULL) delete price; switch(priceCode) { case REGULAR: price = new RegularPrice(); break; case CHILDRENS: price = new ChildrensPrice(); break; case NEW_RELEASE: price = new ReleasePrice(); break; }}double Movie::getCharge(int daysRented){ double ret = 0; if (price != NULL) ret = price->getCharge(daysRented); return ret;}int Movie::getFrequentRenterPoints(int daysRented){ return price->getFrequentRenterPoints(daysRented);}Movie::~Movie(){ delete price;}//********************************租赁类(表示某个顾客租了一部影片)**********************************Rental::Rental(Movie& movie, int daysRented):movie(movie){ this->daysRented = daysRented;}int Rental::getDaysRented(){return daysRented;}Movie& Rental::getMovie(){ return movie;}double Rental::getCharge(){ return movie.getCharge(daysRented);}//将原Customer类的statement中计算常客积分的代码移到Rental类int Rental::getFrequentRenterPoints(){ return movie.getFrequentRenterPoints(daysRented);}//*********************************顾客类(用来表示顾客)*************************************//获得总消费double Customer::getTotalCharge(){ double result = 0; vector<Rental*>::iterator iter = rentals.begin(); while( iter != rentals.end()) { Rental& each = *(*iter); result += each.getCharge(); ++iter; } return result;} //获得总积分int Customer::getTotalFrequentRenterPointers(){ int result = 0; vector<Rental*>::iterator iter = rentals.begin(); while( iter != rentals.end()) { Rental& each = *(*iter); result += each.getFrequentRenterPoints(); ++iter; } return result;}void Customer::cleanUp(){ vector<Rental*>::iterator iter = rentals.begin(); while( iter != rentals.end()) { delete(*iter); ++iter; } rentals.clear();}Customer::Customer(string name){ this->name = name;}void Customer::addRental(Rental* value){ rentals.push_back(value);}string Customer::getName(){return name;}//statement(报表),生成租赁的详单string Customer::statement(){ string ret = "Rental Record for " + name + "\n"; vector<Rental*>::iterator iter = rentals.begin(); while( iter != rentals.end()) { Rental& each = *(*iter); //显示每个租赁记录 ret += "\t" + each.getMovie().getTitle() + "\t" + numToString(each.getCharge())+ "\n"; ++iter; } //增加页脚注释 ret += "Amount owed is " + numToString(getTotalCharge()) + "\n";//用getTotalCharge代替totalAmount ret += "You earned " + numToString(getTotalFrequentRenterPointers()) +"\n"; return ret;}Customer::~Customer(){ cleanUp();}//************************************************初始化数据********************************************void init(Customer& customer){ Movie* mv = new Movie("倚天屠龙记", REGULAR); mv->setPriceCode(NEW_RELEASE); //重新改变影片的类型为NEW_RELEASE //这在上个例子是不可能的! Rental* rt = new Rental(*mv, 2); customer.addRental(rt); mv = new Movie("新水浒传", NEW_RELEASE); rt = new Rental(*mv, 3); customer.addRental(rt); mv = new 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 倚天屠龙记 6 新水浒传 9 喜羊羊与灰太狼 4.5Amount owed is 19.5You earned 5*/
第1章 重构,第一个案例(3):运用多态取代switch