java film_重构Java代码的既有设计-影片出租店

本文通过一个Java案例介绍重构过程,包括计算顾客租赁影片的费用和积分。文章详细展示了如何从原始代码开始,通过分解重组代码块、提取函数以及应用设计模式,如策略模式,来提高代码的可读性和可维护性。
摘要由CSDN通过智能技术生成

案例:计算每位顾客的消费金额并打印详细信息。顾客租赁了哪些影片,租期多长,根据租赁时间和影片类型计算出费用。影片分为3类:儿童片,新片,普通片。此外需计算该顾客的积分。

76593856aef519dc5268803eff52fd60.png

Movie:

48304ba5e6f9fe08f3fa1abda7d326ab.png

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;

}

}

48304ba5e6f9fe08f3fa1abda7d326ab.png

Resume:该顾客租赁了一部影片

48304ba5e6f9fe08f3fa1abda7d326ab.png

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;

}

}

48304ba5e6f9fe08f3fa1abda7d326ab.png

Customer:

租赁费用计算:

影片类型为儿童片,两天以内费用为2,超出两天的时间,每天的费用为1.5

影片类型为新片,每天的费用为3

影片类型为普通片,三天以内费用为1.5,超出三天,每天的费用为1.5

积分计算:

每次租赁影片,积分加一,如果影片为新片且租赁时间大于1天,则多加一分

48304ba5e6f9fe08f3fa1abda7d326ab.png

import java.util.Enumeration;

import java.util.Scanner;

import java.util.Vector;

public class Customer {

private String _name;

private Vector _resume = new Vector(); //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 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);

}

}

48304ba5e6f9fe08f3fa1abda7d326ab.png

现在的代码可以实现基本的功能,当租赁策略、积分策略发生改变时,需要仔细查找statement策略,这时很容易引入bug。那么就很有必要重构之前写的代码。

第一步:为即将修改的代码建立一个可靠的测试环境。

MovieTest

48304ba5e6f9fe08f3fa1abda7d326ab.png

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());

}

}

48304ba5e6f9fe08f3fa1abda7d326ab.png

ResumeTest

48304ba5e6f9fe08f3fa1abda7d326ab.png

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);

}

}

48304ba5e6f9fe08f3fa1abda7d326ab.png

CustomerTest

48304ba5e6f9fe08f3fa1abda7d326ab.png

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);

}

}

48304ba5e6f9fe08f3fa1abda7d326ab.png

第二步:分解重组代码块

statement函数太长了,我们需要分解它,首先将switch语句包装到另外一个函数AmountFor中去,并更改名称使代码更加容易理解

8f900a89c6347c561fdf2122f13be562.pngAmountFor

原来的statement函数改为下面的代码

8f900a89c6347c561fdf2122f13be562.pngStatement

在AmountFor中我们发现它只使用了Resume类,并没有使用到Movie,所以我们将AmountFor函数放在Resume类中,并将函数名改为GetCharge

8f900a89c6347c561fdf2122f13be562.pngGetCharge

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

8f900a89c6347c561fdf2122f13be562.pngtestGetCharge

然后在原来的程序中找到旧函数的所有引用点,然后再用新函数去代替他们

接下来类似“费用计算”我们处理“积分计算”,直接显示修改后的代码

8f900a89c6347c561fdf2122f13be562.pngGetFrequentRenterPoints

8f900a89c6347c561fdf2122f13be562.pngstatement

然后接着提取totalAmount和totalFrequentRenterPoints

8f900a89c6347c561fdf2122f13be562.pngCustomer

最后测试一下修改后的代码

现在你会发现statement函数所做的功能全部是字符串拼接,即界面显示工作,如果需要将结果显示成HTML或者是其他形式,直接添加相同功能函数即可。

第三步:使用类的特性(分装,继承,多态)和设计模式对程序继续重构

switch部分很容易发生修改,因为在修改影片费用策略时就会修改到switch部分,我们现在来重构switch部分

switch部分最好是在自己对象上使用,尽可能的避免在别人的对象上使用。所以这就提示我们需要把switch部分移到movie类中

8f900a89c6347c561fdf2122f13be562.pngMovie

8f900a89c6347c561fdf2122f13be562.pngResume

影片类型有三种,而这三种影片的租赁价格都有其各自的计算方法,所以使用的是策略模式

6cd54e0d8a0d03608a67779b695d4ebd.png

下面是重构以后关于movie修改和新加的内容:

8f900a89c6347c561fdf2122f13be562.pngMovie

8f900a89c6347c561fdf2122f13be562.pngPrice

8f900a89c6347c561fdf2122f13be562.pngNewPrice

8f900a89c6347c561fdf2122f13be562.pngRegularPrice

8f900a89c6347c561fdf2122f13be562.pngChildPrice

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

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值