第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 + "\n";
double totalAmount = ; //总租金额
int frequentReterPoints = ; //常客积分 vector<Rental*>::iterator iter = rentals.begin();
while( iter != rentals.end())
{
double thisAmount = ; //每片需要的租金
Rental& each = *(*iter); //each在switch不会变化,做为参数转给amountFor,而thisAmount作为返回值
thisAmount = amountFor(each); //常客积分
++frequentReterPoints; //如果是新片且租期超过1天以上,则额外送1分积分
if ((each.getMovie().getPriceCode() == Movie::NEW_RELEASE) &&
each.getDaysRented() > ) ++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 = ;//相当于statement中的thisamount; switch(aRental.getMovie().getPriceCode())
{
case Movie::REGULAR:
result += ; //普通片基本租金为2元
if(aRental.getDaysRented() > ) //超过2天的每天加1.5元
result +=(aRental.getDaysRented() - ) * 1.5;
break;
case Movie::NEW_RELEASE:
result += aRental.getDaysRented() * ; //新片的租金
break;
case Movie::CHILDRENS:
result += 1.5; //儿童片基本租金为1.5元
if(aRental.getDaysRented() > ) //超过3天的每天加1.5元
result +=(aRental.getDaysRented() - ) * 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 = ;//相当于statement中的thisamount; switch(getMovie().getPriceCode())
{
case Movie::REGULAR:
result += ; //普通片基本租金为2元
if(getDaysRented() > ) //超过2天的每天加1.5元
result +=(getDaysRented() - ) * 1.5;
break;
case Movie::NEW_RELEASE:
result += getDaysRented() * ; //新片的租金
break;
case Movie::CHILDRENS:
result += 1.5; //儿童片基本租金为1.5元
if(getDaysRented() > ) //超过3天的每天加1.5元
result +=(getDaysRented() - ) * 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 = ; //总租金额
int frequentReterPoints = ; //常客积分 vector<Rental*>::iterator iter = rentals.begin();
while( iter != rentals.end())
{
double thisAmount = ; //每片需要的租金
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() > ) ++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 = ;//相当于statement中的thisamount; switch(getMovie().getPriceCode())
{
case Movie::REGULAR:
result += ; //普通片基本租金为2元
if(getDaysRented() > ) //超过2天的每天加1.5元
result +=(getDaysRented() - ) * 1.5;
break;
case Movie::NEW_RELEASE:
result += getDaysRented() * ; //新片的租金
break;
case Movie::CHILDRENS:
result += 1.5; //儿童片基本租金为1.5元
if(getDaysRented() > ) //超过3天的每天加1.5元
result +=(getDaysRented() - ) * 1.5;
break;
} return result;
} //将原Customer类的statement中计算常客积分的代码移到Rental类
int getFrequentRenterPoints()
{
//如果是新片且租期超过1天以上,则额外送1分积分
if ((getMovie().getPriceCode() == Movie::NEW_RELEASE) &&
getDaysRented() > ) return ;
else return ;
}
}; //顾客类(用来表示顾客)
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 = ; //总租金额
int frequentReterPoints = ; //常客积分 vector<Rental*>::iterator iter = rentals.begin();
while( iter != rentals.end())
{
double thisAmount = ; //每片需要的租金
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 = ; //儿童片
static const int REGULAR = ; //普通片
static const int NEW_RELEASE = ;//新片 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 = ;//相当于statement中的thisamount; switch(getMovie().getPriceCode())
{
case Movie::REGULAR:
result += ; //普通片基本租金为2元
if(getDaysRented() > ) //超过2天的每天加1.5元
result +=(getDaysRented() - ) * 1.5;
break;
case Movie::NEW_RELEASE:
result += getDaysRented() * ; //新片的租金
break;
case Movie::CHILDRENS:
result += 1.5; //儿童片基本租金为1.5元
if(getDaysRented() > ) //超过3天的每天加1.5元
result +=(getDaysRented() - ) * 1.5;
break;
} return result;
} //将原Customer类的statement中计算常客积分的代码移到Rental类
int getFrequentRenterPoints()
{
//如果是新片且租期超过1天以上,则额外送1分积分
if ((getMovie().getPriceCode() == Movie::NEW_RELEASE) &&
getDaysRented() > ) return ;
else return ;
}
}; //顾客类(用来表示顾客)
class Customer
{
private:
string name; //顾客姓名
vector<Rental*> rentals; //每个租赁记录 //获得总消费
double getTotalCharge()
{
double result = ;
vector<Rental*>::iterator iter = rentals.begin();
while( iter != rentals.end())
{
Rental& each = *(*iter); result += each.getCharge(); ++iter;
}
return result;
} //获得总积分
int getTotalFrequentRenterPointers()
{
int result = ; 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, );
customer.addRental(rt); mv = new Movie("新水浒传",Movie::NEW_RELEASE);
rt = new Rental(*mv, );
customer.addRental(rt); mv = new Movie("喜羊羊与灰太狼",Movie::CHILDRENS);
rt = new Rental(*mv, );
customer.addRental(rt);
} int main()
{
Customer customer("SantaClaus");
init(customer); cout << customer.statement() <<endl; return ;
}
/*输出结果
Rental Record for SantaClaus
倚天屠龙记 2
新水浒传 9
喜羊羊与灰太狼 4.5
Amount owed is 15.5
You earned 4
*/
第1章 重构,第一个案例(2):分解并重组statement函数的更多相关文章
- ruby代码重构第一课
(文章是从我的个人主页上粘贴过来的, 大家也可以访问我的主页 www.iwangzheng.com) 新手写代码的时候往往会出现很多重复的代码没有提取出来,大师高瞻远瞩总能提点很多有意义的改进,今天重 ...
- 学习ExtjsForVs(第一个案例HelloWord)
第一个案例-Hello Word 1.本次练习以ext-4.0.7为例,首先从网上下载ext包. 2.打开包后将里面的三个文件或文件夹拷贝到项目中. resource文件夹 bootstrap.js ...
- spring boot实战(第一篇)第一个案例
版权声明:本文为博主原创文章,未经博主允许不得转载. 目录(?)[+] spring boot实战(第一篇)第一个案例 前言 写在前面的话 一直想将spring boot相关内容写成一个系列的 ...
- 第4章 TCP/IP通信案例:访问Internet上的Web服务器
第4章 TCP/IP通信案例:访问Internet上的Web服务器 4.2 部署代理服务器 书中为了演示访问Internet上的Web服务器的全过程,使用了squid代理服务器程序模拟了一个代理服务器 ...
- (转)编写Spring的第一个案例并测试Spring的开发环境
http://blog.csdn.net/yerenyuan_pku/article/details/52832145 Spring4.2.5的开发环境搭建好了之后,我们来编写Spring的第一个案例 ...
- javascript进阶教程第三章--匿名和闭包--案例实战
javascript进阶教程第三章--匿名和闭包--案例实战 一.学习任务 通过几个小练习回顾学过的知识点 二.实例 练习1: 实例描述:打开页面后规定时间内弹出一个新窗口,新窗口指定时间后自动关闭. ...
- Java生鲜电商平台-一次代码重构的实战案例
Java生鲜电商平台-一次代码重构的实战案例 说明,Java开源生鲜电商平台-一次代码重构的实战案例,根据实际的例子,分析出重构与抽象,使代码更加的健壮与高效. 1.业务说明 系统原先已有登录功能,我 ...
- Introduction to 3D Game Programming with DirectX 12 学习笔记之 --- 第十五章:第一人称摄像机和动态索引
原文:Introduction to 3D Game Programming with DirectX 12 学习笔记之 --- 第十五章:第一人称摄像机和动态索引 代码工程地址: https://g ...
- 第7.25节 Python案例详解:使用property函数定义与实例变量同名的属性会怎样?
第7.25节 Python案例详解:使用property函数定义与实例变量同名的属性会怎样? 一. 案例说明 我们上节提到了,使用property函数定义的属性不要与类内已经定义的普通实例变量重 ...
随机推荐
- Windows Server 2012 R2在线安装.NET Framework3.5
Windows Server 2012 (R2) 默认没有安装 .NET Framework 3.5,但可以通过在线安装或指定备用源路径方式. 之前在这个 在Win Server 2012中安装.NE ...
- Redis和Memcached整体
Redis和Memcached整体对比 Redis的作者Salvatore Sanfilippo曾经对这两种基于内存的数据存储系统进行过比较,总体来看还是比较客观的,现总结如下: 1)性能对比:由于R ...
- EC笔记,第二部分:6.若不想使用编译器默认生成的函数,就该明确拒绝
6.若不想使用编译器默认生成的函数,就该明确拒绝 1.有的时候不希望对象被复制和赋值,那么就把复制构造函数与赋值运算符放在private:中,但是这两个函数是否需要实现呢?假设实现了,那么你的类成员方 ...
- 【夯实Mysql基础】记一次mysql语句的优化过程
1. [事件起因] 今天在做项目的时候,发现提供给客户端的接口时间很慢,达到了2秒多,我第一时间,抓了接口,看了运行的sql,发现就是 2个sql慢,分别占了1秒多. 一个sql是 链接了5个表同时使 ...
- PHP 字符串左边补0,字符串右边补0
概述:项目中经常会使用到在一串编码左边.右边甚至中间自动填充制定字符如"0" 并且制定填充后的字符串长度. 函数str_pad:该函数返回 input 被从左端.右端或者同时两端被 ...
- 关于移动端meta设置
<meta name="viewport" content="width=device-width, target-densitydpi=160dpi, initi ...
- 【一次面试】再谈javascript中的继承
前言 面向对象编程是每次面试必问的知识点,而前端js如何实现继承每次命中率高达80% 这不,近两天我们面试时候,同事就问道面试者此问题,但是,不论之前自己做的回答,还是面试者的回答,基本都不太令人满意 ...
- javascript 函数初探 (一)--- 神马是函数
神马是函数? 所谓函数,本质上是一种代码的分组形式.我们可以通过这种形式赋予某组代码一个名字,以便与之后的调用.下面,我们来示范以下函数的声明: function sum(a, b){ var c = ...
- 两种JavaScript的AES加密方式(可与Java相互加解密)
由于JavaScript属于弱类型脚本语言,因此当其与强类型的后台语言进行数据交互时会产生各种问题,特别是加解密的操作.本人由于工作中遇到用js与Java进行相互加解密的问题,在网上查了很多资料及代码 ...
- iOS 苹果开发证书失效的解决方案(Failed to locate or generate matching signing assets)
从2月14日开始,上传程序的同学可能会遇到提示上传失败的提示. 并且打开自己的钥匙串,发现所有的证书全部都显示此证书签发者无效. 出现以下情况: Failed to locate or generat ...