案例:计算每位顾客的消费金额并打印详细信息。顾客租赁了哪些影片,租期多长,根据租赁时间和影片类型计算出费用。影片分为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 customer public 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 record
Resume each = (Resume) resumes.nextElement();
// the movie's type
switch(each.get_movie().get_priceCode()){
case Movie.CHILD:
thisAmount += 2; //the basic fee is 2
if(each.get_daysRented() > 2){
//the day is more than 2
thisAmount += (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.5
if(each.get_daysRented() > 3){
//the day is more than 3
thisAmount += (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); @Before
public void setUp() throws Exception {
} @After
public void tearDown() throws Exception {
} @Test
public void testGet_title() {
assertEquals("fall in love",m0.get_title());
} @Test
public 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); @Before
public void setUp() throws Exception {
} @After
public void tearDown() throws Exception {
} @Test
public void testGet_movie() {
Movie m3 = new Movie("three children and their mother",2);
assertEquals(m3.get_title(),r2.get_movie().get_title());
} @Test
public 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"); @Test
public void testAddRental() {
c1.addRental(r1);
} @Test
public void testGetName() {
String testname = "abby";
assertEquals(testname, c1.getName());
} @Test
public 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 record
switch(resume.get_movie().get_priceCode()){
case Movie.CHILD:
result += 2; //the basic fee is 2
if(resume.get_daysRented() > 2){
//the day is more than 2
result += (resume.get_daysRented() - 2) * 1.5;
}
break;
case Movie.NEW:
result += resume.get_daysRented() * 3; //the basic fee is 2
break;
case Movie.REGULAR:
result += 1.5; //the basic fee is 1.5
if(resume.get_daysRented() > 3){
//the day is more than 3
result += (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 resume
double 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 record
switch(get_movie().get_priceCode()){
case Movie.CHILD:
result += 2; //the basic fee is 2
if(get_daysRented() > 2){
//the day is more than 2
result += (get_daysRented() - 2) * 1.5;
}
break;
case Movie.NEW:
result += get_daysRented() * 3; //the basic fee is 2
break;
case Movie.REGULAR:
result += 1.5; //the basic fee is 1.5
if(get_daysRented() > 3){
//the day is more than 3
result += (get_daysRented() - 3) * 1.5;
}
break;
}
return result;
} }

GetCharge

同时添加新的函数测试代码

public class ResumeTest {
......
@Test
public 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 resumes
double 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 resumes
int 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 record
switch(this.get_priceCode()){
case Movie.CHILD:
result += 2; //the basic fee is 2
if(dayRent > 2){
//the day is more than 2
result += (dayRent - 2) * 1.5;
}
break;
case Movie.NEW:
result += dayRent * 3; //the basic fee is 2
break;
case Movie.REGULAR:
result += 1.5; //the basic fee is 1.5
if(dayRent > 3){
//the day is more than 3
result += (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;
else
return 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;
else
return 1;
}
}

Movie

public abstract class Price {
abstract int getPriceCode();
abstract double getCharge(int dayRent);
}

Price

public class NewPrice extends Price {

    @Override
int getPriceCode() {
// TODO Auto-generated method stub
return Movie.NEW;
} @Override
double getCharge(int dayRent) {
return dayRent * 3;
} }

NewPrice

public class RegularPrice extends Price {

    @Override
int getPriceCode() {
// TODO Auto-generated method stub
return Movie.REGULAR;
} @Override
double getCharge(int dayRent) {
double result = 1.5; //the basic fee is 1.5
if(dayRent > 3){ //the day is more than 3
result += (dayRent - 3) * 1.5;
}
return result;
} }

RegularPrice

public class ChildPrice extends Price {

    @Override
int getPriceCode() {
// TODO Auto-generated method stub
return Movie.CHILD;
} @Override
double getCharge(int dayRent) {
double result = 2; //the basic fee is 2
if(dayRent > 2){ //the day is more than 2
result += (dayRent - 2) * 1.5;
}
return result;
} }

ChildPrice

其实重构就是不断的测试修改的过程。

重构Java代码的既有设计-影片出租店的更多相关文章

  1. 怎样编写高质量的java代码

    代码质量概述     怎样辨别一个项目代码写得好还是坏?优秀的代码和腐化的代码区别在哪里?怎么让自己写的代码既漂亮又有生命力?接下来将对代码质量的问题进行一些粗略的介绍.也请有过代码质量相关经验的朋友 ...

  2. 敏捷开发中高质量 Java 代码开发实践

    Java 项目开发过程中,由于开发人员的经验.代码风格各不相同,以及缺乏统一的标准和管理流程,往往导致整个项目的代码质量较差,难于维护,需要较大的测试投入 和周期等问题. 这些问题在一个项目组初建.需 ...

  3. 如何在Android上编写高效的Java代码

    转自:http://www.ituring.com.cn/article/177180 作者/ Erik Hellman Factor10咨询公司资深移动开发顾问,曾任索尼公司Android团队首席架 ...

  4. 老司机告诉你高质量的Java代码是怎么练成的?

    一提起程序员,首先想到的一定是"码农",对,我们是高产量的优质"码农",我们拥有超跃常人的逻辑思维以及不走寻常路的分析.判别能力,当然,我们也有良好的编码规范, ...

  5. 写出优质Java代码的4个技巧(转)

    http://geek.csdn.net/news/detail/238243 原文:4 More Techniques for Writing Better Java 作者:Justin Alban ...

  6. java代码分析及分析工具

    一个项目从搭建开始,开发的初期往往思路比较清晰,代码也比较清晰.随着时间的推移,业务越来越复杂.代码也就面临着耦合,冗余,甚至杂乱,到最后谁都不敢碰. 作为一个互联网电子商务网站的业务支撑系统,业务复 ...

  7. 高效重构 C++ 代码

    引言 Martin Fowler的<重构:改善既有代码的设计>一书从2003年问世至今已有十几年时间了,按照计算机领域日新月异的变化速度,重构已经算是一门陈旧的技术了.但是陈旧并不代表不重 ...

  8. 怎样编写高质量的 Java 代码

    代码质量概述 怎样辨别一个项目代码写得好还是坏?优秀的代码和腐化的代码区别在哪里?怎么让自己写的代码既漂亮又有生命力?接下来将对代码质量的问题进行一些粗略的介绍.也请有过代码质量相关经验的朋友提出宝贵 ...

  9. 适用于Java开发人员的SOLID设计原则简介

    看看这篇针对Java开发人员的SOLID设计原则简介.抽丝剥茧,细说架构那些事——[优锐课] 当你刚接触软件工程时,这些原理和设计模式不容易理解或习惯.我们都遇到了问题,很难理解SOLID + DP的 ...

随机推荐

  1. centos7安装kvm环境采用网桥模式并创建虚拟机制作openstack需要的镜像

    初始环境的安装:centos7 mini iso镜像进行安装的系统 采用的环境是vm该软件,联网方式NAT模式下配置的静态ip(如何在NAT模式下配置静态ip参考之前的文章) 1.由于要安装kvm环境 ...

  2. 《JAVA程序设计》第五次实验报告

    20145333 实验五 Java网络编程及安全 北京电子科技学院(BESTI)实验报告 课程:Java程序设计 班级:1453 指导教师:娄嘉鹏 实验日期:2016.05.06 18:30-21:3 ...

  3. 访问ashx一般应用程序

    浏览器中的地址栏键入要访问页面的地址:回车(是和服务器软件打交道)----向服务器发送请求(以http协议为基础,服务器按照此协议解释理解接收到的数据),服务器接收到发送的请求,根据请求信息知道当前所 ...

  4. 10个足以让你成为更优秀的程序员的C语言资源

    一些人觉得编程无聊,一些人觉得它很好玩.但每个程序员都必须紧跟编程语言的潮流.大多数程序员都是从C开始学习编程的,因为C是用来写操作系统.应用程序最常用的语言. · C编程笔记 这些是华盛顿实验学院C ...

  5. PHP设计模式(六): 单例模式

  6. 解题报告:hdu 1276 士兵队列训练问题 - 简单题

    Problem Description 某部队进行新兵队列训练,将新兵从一开始按顺序依次编号,并排成一行横队,训练的规则如下:从头开始一至二报数,凡报到二的出列,剩下的向小序号方向靠拢,再从头开始进行 ...

  7. R中去除为NA的行--转载

    下面用实例来说明这两个函数的作用: 这是一个数据框final: gene hsap mmul mmus rnor cfam 1 ENSG00000208234 0 NA NA NA NA 2 ENSG ...

  8. 关于Vue的component制作dialog组件

    其实原理很简单,兴个粟子, 点击按钮出现 dialog 弹出杠, 将dialog做成一个组件,components/dialog.vue 就是在components里面新建一个vue.将这个vue做为 ...

  9. Warsaw U Contest Petrozavo dsk Summer 2011 Training Camp, Monday, September 5, 2011

    Warsaw U Contest Petrozavo dsk Summer 2011 Training Camp, Monday, September 5, 2011 Problem A.Chocol ...

  10. 数据库原理及应用-数据库管理系统 DBMS

    2018-02-20 14:35:34 数据库管理系统(英语:database management system,缩写:DBMS) 是一种针对对象数据库,为管理数据库而设计的大型电脑软件管理系统.具 ...