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对象中获得某个总量。可以利用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 = ; //儿童片
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函数的更多相关文章

  1. ruby代码重构第一课

    (文章是从我的个人主页上粘贴过来的, 大家也可以访问我的主页 www.iwangzheng.com) 新手写代码的时候往往会出现很多重复的代码没有提取出来,大师高瞻远瞩总能提点很多有意义的改进,今天重 ...

  2. 学习ExtjsForVs(第一个案例HelloWord)

    第一个案例-Hello Word 1.本次练习以ext-4.0.7为例,首先从网上下载ext包. 2.打开包后将里面的三个文件或文件夹拷贝到项目中. resource文件夹 bootstrap.js ...

  3. spring boot实战(第一篇)第一个案例

    版权声明:本文为博主原创文章,未经博主允许不得转载.   目录(?)[+]   spring boot实战(第一篇)第一个案例 前言 写在前面的话 一直想将spring boot相关内容写成一个系列的 ...

  4. 第4章 TCP/IP通信案例:访问Internet上的Web服务器

    第4章 TCP/IP通信案例:访问Internet上的Web服务器 4.2 部署代理服务器 书中为了演示访问Internet上的Web服务器的全过程,使用了squid代理服务器程序模拟了一个代理服务器 ...

  5. (转)编写Spring的第一个案例并测试Spring的开发环境

    http://blog.csdn.net/yerenyuan_pku/article/details/52832145 Spring4.2.5的开发环境搭建好了之后,我们来编写Spring的第一个案例 ...

  6. javascript进阶教程第三章--匿名和闭包--案例实战

    javascript进阶教程第三章--匿名和闭包--案例实战 一.学习任务 通过几个小练习回顾学过的知识点 二.实例 练习1: 实例描述:打开页面后规定时间内弹出一个新窗口,新窗口指定时间后自动关闭. ...

  7. Java生鲜电商平台-一次代码重构的实战案例

    Java生鲜电商平台-一次代码重构的实战案例 说明,Java开源生鲜电商平台-一次代码重构的实战案例,根据实际的例子,分析出重构与抽象,使代码更加的健壮与高效. 1.业务说明 系统原先已有登录功能,我 ...

  8. Introduction to 3D Game Programming with DirectX 12 学习笔记之 --- 第十五章:第一人称摄像机和动态索引

    原文:Introduction to 3D Game Programming with DirectX 12 学习笔记之 --- 第十五章:第一人称摄像机和动态索引 代码工程地址: https://g ...

  9. 第7.25节 Python案例详解:使用property函数定义与实例变量同名的属性会怎样?

    第7.25节 Python案例详解:使用property函数定义与实例变量同名的属性会怎样? 一.    案例说明 我们上节提到了,使用property函数定义的属性不要与类内已经定义的普通实例变量重 ...

随机推荐

  1. Sql server函数的学习1(系统变量、错误函数、转换函数)

    一.系统变量的介绍和使用 1.@@ERROR 变量 2.@@SERVICENAME 变量 3.@@TOTAL_ERRORS 变量 4.@@TOTAL_READ 变量 5.@@VERSION 变量 二. ...

  2. 背水一战 Windows 10 (25) - MVVM: 通过 x:Bind 实现 MVVM(不用 Command)

    [源码下载] 背水一战 Windows 10 (25) - MVVM: 通过 x:Bind 实现 MVVM(不用 Command) 作者:webabcd 介绍背水一战 Windows 10 之 MVV ...

  3. Integer 与int 的区别

    int 是java 提供的8 种原始数据类型之一.Java 为每个原始类型提供了封装类,Integer 是java 为int 提供的封装类.int 的默认值为0,而Integer 的默认值为null, ...

  4. ASP.NET MVC搭建项目后台UI框架—5、Demo演示Controller和View的交互

    目录 ASP.NET MVC搭建项目后台UI框架—1.后台主框架 ASP.NET MVC搭建项目后台UI框架—2.菜单特效 ASP.NET MVC搭建项目后台UI框架—3.面板折叠和展开 ASP.NE ...

  5. Xdebug文档(七) 远程调试

    Xdebug提示调试客户端接口与PHP运行相联系.该章节解释如何设置PHP与xdebug,并推荐一些客户端. 介绍 Xdebug的远程调试器允许你检查数据结构,交互式地穿过和调试你的代码.该协议已经开 ...

  6. 自定义JsonResult解决 序列化类型 System.Data.Entity.DynamicProxies 的对象时检测到循环引用

    接上篇的问题,给出我自己的解决方案. 同时推荐要学习MVC的可以参考下<ASP.NET MVC4 框架揭秘>. 首先,要自定义JSonResult,就要明白MVC中 JsonResult的 ...

  7. 软件开发流程 Software development process

    软件开发流程(Software development process)即软件设计思路和方法的一般过程,包括设计软件的功能和实现的算法和方法.软件的总体结构设计和模块设计.编程和调试.程序联调和测试以 ...

  8. file_put_contents 错误:failed to open stream: Invalid argument 一种原因

    今天在测试nilcms系统的时候,出现了一个报错,导致缓存无法更新: file_put_contents(C:\UPUPW_AP5.4\vhosts\d.tv\NilCMS_APP\include_r ...

  9. Ajax表单异步上传(包括文件域)

    起因 做前台页面时,需要调用WebAPI的Post请求,发送一些字段和文件(相当于把表单通过ajax异步发送出去,得到返回结果),然后得到返回值判断是否成功. 尝试 先是尝试了一下 "jQu ...

  10. android TextView多行文本(超过3行)使用ellipsize="end"属性无效问题的解决方法

    <TextView android:id="@+id/desc" android:layout_width="match_parent" android: ...