重构Java代码的既有设计-影片出租店
案例:计算每位顾客的消费金额并打印详细信息。顾客租赁了哪些影片,租期多长,根据租赁时间和影片类型计算出费用。影片分为3类:儿童片,新片,普通片。此外需计算该顾客的积分。
Movie:
public class Movie {//电影类型public static final int CHILD = 2;public static final int NEW = 3;public static final int REGULAR = 1;private String _title;private int _priceCode;public Movie(String title,int priceCode) {this._title = title;this._priceCode = priceCode;}public String get_title() {return _title;}/*** 获取影片类型* @return*/public int get_priceCode() {return _priceCode;} }
Resume:该顾客租赁了一部影片
public class Resume {private Movie _movie;private int _daysRented;public Resume(Movie movie,int daysRented) {this._movie = movie;this._daysRented = daysRented;}public Movie get_movie() {return _movie;}public int get_daysRented() {return _daysRented;}}
Customer:
租赁费用计算:
影片类型为儿童片,两天以内费用为2,超出两天的时间,每天的费用为1.5
影片类型为新片,每天的费用为3
影片类型为普通片,三天以内费用为1.5,超出三天,每天的费用为1.5
积分计算:
每次租赁影片,积分加一,如果影片为新片且租赁时间大于1天,则多加一分
import java.util.Enumeration; import java.util.Scanner; import java.util.Vector;public class Customer {private String _name;private Vector<Resume> _resume = new Vector<Resume>(); //all resume by this customerpublic Customer(String name){this._name = name;}/*** add resume info* @param arg*/public void addRental(Resume arg){this._resume.addElement(arg);}public String getName(){return this._name;}/*** get all result(include time,movie type,fee of each resume and all fee)* @return result*/public String statement(){double totalAmount = 0;int frequentRenterPoints = 0; //the all collectPoint; Enumeration<Resume> resumes = this._resume.elements(); //all record of resumes String result = "Rental Record for" +"\t" + this.getName() + "\n";while(resumes.hasMoreElements()){double thisAmount = 0; // fee of this recordResume each = (Resume) resumes.nextElement();// the movie's typeswitch(each.get_movie().get_priceCode()){case Movie.CHILD:thisAmount += 2; //the basic fee is 2if(each.get_daysRented() > 2){//the day is more than 2thisAmount += (each.get_daysRented() - 2) * 1.5;}break;case Movie.NEW:thisAmount += each.get_daysRented() * 3; break;case Movie.REGULAR:thisAmount += 1.5; //the basic fee is 1.5if(each.get_daysRented() > 3){//the day is more than 3thisAmount += (each.get_daysRented() - 3) * 1.5;}break;}frequentRenterPoints ++;if((each.get_movie().get_priceCode() == Movie.NEW)&&(each.get_daysRented() > 1)){frequentRenterPoints ++;}result += "\t" + each.get_movie().get_title() + "\t" + String.valueOf(thisAmount) + "\n";totalAmount += thisAmount;}result += "Amount owed is" + "\t" + String.valueOf(totalAmount) + "\n";result += "You earned "+ String.valueOf(frequentRenterPoints) + " frequent renter points";return result;}@SuppressWarnings("resource")public static void main(String arg[]){Scanner sc = new Scanner(System.in);System.out.println("please input your name:"+"\n");String c_name = sc.nextLine();Customer c1 = new Customer(c_name);System.out.println("please input the movie name:"+"\n");String m_name = sc.nextLine();System.out.println("please input the movie type:"+ "\n");System.out.println("1.regular movie"+"\n"+"2.child movie"+"\n"+"3.new movie"+"\n");int type = sc.nextInt();Movie m1 = new Movie(m_name,type);System.out.println("please input the time you have rent:"+"\n");int day = sc.nextInt();Resume r1 = new Resume(m1,day);c1.addRental(r1);String ans= c1.statement();System.out.println(ans);} }
现在的代码可以实现基本的功能,当租赁策略、积分策略发生改变时,需要仔细查找statement策略,这时很容易引入bug。那么就很有必要重构之前写的代码。
第一步:为即将修改的代码建立一个可靠的测试环境。
MovieTest
import static org.junit.Assert.*;import org.junit.After; import org.junit.Before; import org.junit.Test;public class MovieTest {Movie m0 = new Movie("fall in love",3);@Beforepublic void setUp() throws Exception {}@Afterpublic void tearDown() throws Exception {}@Testpublic void testGet_title() {assertEquals("fall in love",m0.get_title());}@Testpublic void testGet_priceCode() {assertEquals(3,m0.get_priceCode());}}
ResumeTest
import static org.junit.Assert.*;import org.junit.After; import org.junit.Before; import org.junit.Test;public class ResumeTest {Movie m2 = new Movie("three children and their mother",2);Resume r2 = new Resume(m2,3);@Beforepublic void setUp() throws Exception {}@Afterpublic void tearDown() throws Exception {}@Testpublic void testGet_movie() {Movie m3 = new Movie("three children and their mother",2);assertEquals(m3.get_title(),r2.get_movie().get_title());}@Testpublic void testGet_daysRented() {assertEquals(r2.get_daysRented(),3);}}
CustomerTest
import static org.junit.Assert.*; import org.junit.Test;public class CustomerTest {Movie m1 = new Movie("123435",1);Resume r1 = new Resume(m1,4);Customer c1 = new Customer("abby");@Testpublic void testAddRental() {c1.addRental(r1);}@Testpublic void testGetName() {String testname = "abby";assertEquals(testname, c1.getName());}@Testpublic void testStatement() {String testResult = "Rental Record for abby"+"\n\t"+"123435 3.0"+"\n"+"Amount owed is 3.0"+"\n"+"You earned 1 frequent renter points";c1.addRental(r1);String realResult = c1.statement();assertEquals(testResult,realResult);} }
第二步:分解重组代码块
statement函数太长了,我们需要分解它,首先将switch语句包装到另外一个函数AmountFor中去,并更改名称使代码更加容易理解
/*** calculate amount fee for this resume* @param resume* @return*/private double AmountFor(Resume resume){double result = 0; // fee of this recordswitch(resume.get_movie().get_priceCode()){case Movie.CHILD:result += 2; //the basic fee is 2if(resume.get_daysRented() > 2){//the day is more than 2result += (resume.get_daysRented() - 2) * 1.5;}break;case Movie.NEW:result += resume.get_daysRented() * 3; //the basic fee is 2break;case Movie.REGULAR:result += 1.5; //the basic fee is 1.5if(resume.get_daysRented() > 3){//the day is more than 3result += (resume.get_daysRented() - 3) * 1.5;}break;}return result;}
AmountFor
原来的statement函数改为下面的代码
/*** get all result(include time,movie type,fee of each resume and all fee)* @return result*/public String statement(){double totalAmount = 0;int frequentRenterPoints = 0; //the all collectPoint; Enumeration<Resume> resumes = this._resume.elements(); //all record of resumes String result = "Rental Record for" +"\t" + this.getName() + "\n";while(resumes.hasMoreElements()){Resume each = resumes.nextElement();// get amount for each resumedouble thisAmount = this.AmountFor(each);frequentRenterPoints ++;if((each.get_movie().get_priceCode() == Movie.NEW)&&(each.get_daysRented() > 1)){frequentRenterPoints ++;}result += "\t" + each.get_movie().get_title() + "\t" + String.valueOf(thisAmount) + "\n";totalAmount += thisAmount;}result += "Amount owed is" + "\t" + String.valueOf(totalAmount) + "\n";result += "You earned "+ String.valueOf(frequentRenterPoints) + " frequent renter points";return result;}
Statement
在AmountFor中我们发现它只使用了Resume类,并没有使用到Movie,所以我们将AmountFor函数放在Resume类中,并将函数名改为GetCharge
public class Resume {....../*** calculate charge for this resume* @return*/public double GetCharge(){double result = 0; // fee of this recordswitch(get_movie().get_priceCode()){case Movie.CHILD:result += 2; //the basic fee is 2if(get_daysRented() > 2){//the day is more than 2result += (get_daysRented() - 2) * 1.5;}break;case Movie.NEW:result += get_daysRented() * 3; //the basic fee is 2break;case Movie.REGULAR:result += 1.5; //the basic fee is 1.5if(get_daysRented() > 3){//the day is more than 3result += (get_daysRented() - 3) * 1.5;}break;}return result;}}
GetCharge
同时添加新的函数测试代码
public class ResumeTest {......@Testpublic void testGetCharge() {assertEquals(String.valueOf(r2.GetCharge()),String.valueOf(3.5));} }
testGetCharge
然后在原来的程序中找到旧函数的所有引用点,然后再用新函数去代替他们
接下来类似“费用计算”我们处理“积分计算”,直接显示修改后的代码
public class Resume {....../*** get FrequentRenterPoints for this resume* @return*/public int GetFrequentRenterPoints(){int result = 0;result ++;if((get_movie().get_priceCode() == Movie.NEW)&&(get_daysRented() > 1)){result ++;}return result;}}
GetFrequentRenterPoints
/*** get all result(include time,movie type,fee of each resume and all fee)* @return result*/public String statement(){double totalAmount = 0;int frequentRenterPoints = 0; //the all collectPoint; Enumeration<Resume> resumes = this._resume.elements(); //all record of resumes String result = "Rental Record for" +"\t" + this.getName() + "\n";while(resumes.hasMoreElements()){Resume each = resumes.nextElement();frequentRenterPoints += each.GetFrequentRenterPoints();totalAmount += each.GetCharge();result += "\t" + each.get_movie().get_title() + "\t" + String.valueOf(each.GetCharge()) + "\n";}result += "Amount owed is" + "\t" + String.valueOf(totalAmount) + "\n";result += "You earned "+ String.valueOf(frequentRenterPoints) + " frequent renter points";return result;}
statement
然后接着提取totalAmount和totalFrequentRenterPoints
public class Customer {....../*** get total charge* @return*/private double GetTotalCharge(){Enumeration<Resume> resumes = this._resume.elements(); //all record of resumesdouble result = 0;while(resumes.hasMoreElements()){Resume each = resumes.nextElement();result += each.GetCharge();}return result;}/*** get total frequentRenterPoints* @return*/private int GetTotalFrequentRenterPoints(){Enumeration<Resume> resumes = this._resume.elements(); //all record of resumesint result = 0;while(resumes.hasMoreElements()){Resume each = resumes.nextElement();result += each.GetFrequentRenterPoints();}return result;}/*** get all result(include time,movie type,fee of each resume and all fee)* @return result*/public String statement(){Enumeration<Resume> resumes = this._resume.elements(); //all record of resumes String result = "Rental Record for" +"\t" + this.getName() + "\n";while(resumes.hasMoreElements()){Resume each = resumes.nextElement();result += "\t" + each.get_movie().get_title() + "\t" + String.valueOf(each.GetCharge()) + "\n";}result += "Amount owed is" + "\t" + String.valueOf(GetTotalCharge()) + "\n";result += "You earned "+ String.valueOf(GetTotalFrequentRenterPoints()) + " frequent renter points";return result;}}
Customer
最后测试一下修改后的代码
现在你会发现statement函数所做的功能全部是字符串拼接,即界面显示工作,如果需要将结果显示成HTML或者是其他形式,直接添加相同功能函数即可。
第三步:使用类的特性(分装,继承,多态)和设计模式对程序继续重构
switch部分很容易发生修改,因为在修改影片费用策略时就会修改到switch部分,我们现在来重构switch部分
switch部分最好是在自己对象上使用,尽可能的避免在别人的对象上使用。所以这就提示我们需要把switch部分移到movie类中
public class Movie {....../*** calculate charge for resume* @return*/public double GetCharge(int dayRent){double result = 0; // fee of this recordswitch(this.get_priceCode()){case Movie.CHILD:result += 2; //the basic fee is 2if(dayRent > 2){//the day is more than 2result += (dayRent - 2) * 1.5;}break;case Movie.NEW:result += dayRent * 3; //the basic fee is 2break;case Movie.REGULAR:result += 1.5; //the basic fee is 1.5if(dayRent > 3){//the day is more than 3result += (dayRent - 3) * 1.5;}break;}return result;}/*** get FrequentRenterPoints for resume* @return*/public int GetFrequentRenterPoints(int dayRent){if((get_priceCode() == Movie.NEW)&&(dayRent > 1))return 2;elsereturn 1;} }
Movie
public class Resume {....../*** calculate charge for this resume* @return*/public double GetCharge(){return _movie.GetCharge(this._daysRented);}/*** get FrequentRenterPoints for this resume* @return*/public int GetFrequentRenterPoints(){return _movie.GetFrequentRenterPoints(_daysRented);}}
Resume
影片类型有三种,而这三种影片的租赁价格都有其各自的计算方法,所以使用的是策略模式
下面是重构以后关于movie修改和新加的内容:
public class Movie {//电影类型public static final int CHILD = 2;public static final int NEW = 3;public static final int REGULAR = 1;private String _title;private int _priceCode; //影片类型private Price _price;public Movie(String title,int priceCode) {this._title = title;this._priceCode = priceCode;set_priceCode();}public String get_title() {return _title;}public int get_priceCode() {return _price.getPriceCode();}public void set_priceCode() {switch(_priceCode){case Movie.CHILD:_price = new ChildPrice();break;case Movie.NEW:_price = new NewPrice();break;case Movie.REGULAR:_price = new RegularPrice();break;}}/*** calculate charge for resume* @return*/public double GetCharge(int dayRent){return _price.getCharge(dayRent);}/*** get FrequentRenterPoints for resume* @return*/public int GetFrequentRenterPoints(int dayRent){if((get_priceCode() == Movie.NEW)&&(dayRent > 1))return 2;elsereturn 1;} }
Movie
public abstract class Price {abstract int getPriceCode();abstract double getCharge(int dayRent); }
Price
public class NewPrice extends Price {@Overrideint getPriceCode() {// TODO Auto-generated method stubreturn Movie.NEW;}@Overridedouble getCharge(int dayRent) {return dayRent * 3;}}
NewPrice
public class RegularPrice extends Price {@Overrideint getPriceCode() {// TODO Auto-generated method stubreturn Movie.REGULAR;}@Overridedouble getCharge(int dayRent) {double result = 1.5; //the basic fee is 1.5if(dayRent > 3){ //the day is more than 3result += (dayRent - 3) * 1.5;}return result;}}
RegularPrice
public class ChildPrice extends Price {@Overrideint getPriceCode() {// TODO Auto-generated method stubreturn Movie.CHILD;}@Overridedouble getCharge(int dayRent) {double result = 2; //the basic fee is 2if(dayRent > 2){ //the day is more than 2result += (dayRent - 2) * 1.5;}return result;}}
ChildPrice
其实重构就是不断的测试修改的过程。
转载于:https://www.cnblogs.com/sker/p/6724000.html
重构Java代码的既有设计-影片出租店相关推荐
- 避免代码冗余,使用接口和泛型重构Java代码
转载自 避免代码冗余,使用接口和泛型重构Java代码 在使用动态语言和.NET工作了若干年后,我又回到老本行–Java开发.在Ruby中,清除代码冗余是非常方便的,而在Java中则需要结合接口和泛型实 ...
- 简单的Java代码测试样例设计
简单的Java代码测试样例设计 函数一 public static int findLast(int[] x, int y) {for (int i = x.length - 1; i > 0; ...
- 重构 java代码_java代码重构的方法学习
2,8阅读 第一章 第一个案例 在此案例中使用了, 方法的提取(extract method),方法的移动(move method),临时变量的清除,变量及函数名重构,switch类型重构(repl ...
- 基于Jsp、Java、数据库、HTML实现网上投票系统(含文档和代码)Jsp课程设计
目录 摘要 第1章 课程设计内容及要求 第2章 系统分析 2.1 系统简介 2.2 系统功能 第3章 系统设计 3.1 开发工具简介 3.2 总体功能模块设计 第4章 系统实现 4.1 数据库实现 4 ...
- java代码重构工具_代码重构什么意思 Java代码重构的几种模式
指对软件代码做任何更动以增加可读性或者简化结构而不影响输出结果. 软件重构需要借助工具完成,重构工具能够修改代码同时修改所有引用该代码的地方.在极限编程的方法学中,重构需要单元测试来支持. 在软件工程 ...
- java代码重构原则_重构原则
重构原则 重构:对软件内部结构的一种调整,目的是在不改变软件客观察行为的前提下,提高其可理解性,降低修改成本. 何为重构 重构改进软件的设计.如果没有重构,程序的设计会逐渐腐败变质.而改进设计的一个重 ...
- java重复代码重构_重构重复代码
java重复代码重构 As a software engineer working on a large project, you'll likely be asked to do cleanup w ...
- 计算机课程设计-基于ssm+vue的物资管理系统(前后端分离)-物资出库入库管理系统java代码
计算机课程设计-基于ssm+vue的物资管理系统(前后端分离)-物资出库入库管理系统java代码 注意:该项目只展示部分功能,如需了解,评论区咨询即可. 作者:IT跃迁谷 1.开发环境 开发语言:Ja ...
- 五子棋游戏Java代码简单实现(含活动图和类图设计)
五子棋游戏Java代码简单实现(含活动图和类图设计) 文章目录 五子棋游戏Java代码简单实现(含活动图和类图设计) 活动图设计 类图设计 代码实现 总结 OOA和OOD设计 代码设计 可改进部分 活 ...
最新文章
- 前端开发之JavaScript基础篇一
- Ubuntu画图工具
- 这 10 个简单的面试题,却隐藏大坑,大厂的套路防不胜防
- 2017 年 VR 将走的 3 个方向 你更认可哪一个?
- PHP 中 9 大缓存技术总结
- html怎么在页面中获取操作用户的id,CSS ID 选择器详解
- This is very likely to create a memory leak.
- Windows 内核(WRK)简介
- JavaFX UI控件教程(二十五)之Color Picker
- CString TCHAR互相转换
- GitHub中README.md文件的编辑
- Linux磁盘读写速率测试
- PAT (Basic Level) Practice (中文)答案合集
- php的socket,PHP Socket范例
- ScalaReact式编程书
- linux嵌入式6818,嵌入式ARM实验箱(FS_6818M4)_华清远见研发中心
- Delphi 2010 新增功能之: 手势编程[5] - 关于自定义手势
- 计算机毕业论文个人小结2500字,毕业论文个人小结
- cocos2dx 自定义事件
- FileReader类
热门文章
- 揣测一下未来的政策走向
- SC16IS752调试
- T430系统盘迁移到SSD上的经验
- 【每日最爱一句】2013.07.31
- #腾讯云·未来开发者云梯计划#第三期上线啦!全国5000个免费云认证培训考试名额开放报名中!
- 计算机用户域怎么删除,如何删除域内非活动计算机账号?
- Your task is to Calculate a + b.
- 网络营销中促销的含义、特点和功能
- Cantor‘s paradox
- 计算机毕设凑不够字数,撰写毕业论文字数不够要怎样去增加?