这是一个非常简单的案例,展示了一个影片出租店用的程序,计算每一位顾客的消费金额并打印详单的模块,同时还需要计算每一位客人的积分。
抽象了三个实体:影片(片名、片类型)、租赁(影片、租赁天数)、顾客(姓名、租赁清单),
代码详见下面的三个类以及测试类。
customer.java
mport java.util.Enumeration;
import java.util.Vector;
public class Customer {
private String name;
private Vector rentals = new Vector<>();
public Customer(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void addRental(Rental rental) {
rentals.add(rental);
}
public String statement() {
double totalAmount = 0;
int frequentRenterPointers = 0;
Enumeration rentalEnumeration = rentals.elements();
String result = "Rental Records for " + getName() + "\n";
while (rentalEnumeration.hasMoreElements()) {
double thisAmount = 0;
Rental each = rentalEnumeration.nextElement();
// determine amounts for each line
switch (each.getMovie().getPriceCode()) {
case Movie.REGULAR:
thisAmount += 2;
if (each.getDayRented() > 2) {
thisAmount += (each.getDayRented() - 2) * 1.5;
}
break;
case Movie.NEW_RELEASE:
thisAmount += each.getDayRented() * 3;
break;
case Movie.CHILDREN:
thisAmount += 1.5;
if (each.getDayRented() > 3) {
thisAmount += (each.getDayRented() - 3) * 1.5;
}
break;
}
// add frequent renter points
frequentRenterPointers++;
// add bonus for two day new release rental
if ((each.getMovie().getPriceCode() == Movie.NEW_RELEASE) && each.getDayRented() > 1) {
frequentRenterPointers++;
}
//show figures for this rental
result += "\t" + each.getMovie().getTitle() + "\t" + thisAmount + "\n";
totalAmount += thisAmount;
}
// add footer lines
result += "Amount owed is " + totalAmount + "\n";
result += "You earned " + frequentRenterPointers + " frequent renter points";
return result;
}
}
Movie.java
public class Movie {
public static final int REGULAR = 0;
public static final int NEW_RELEASE = 1;
public static final int CHILDREN = 2;
private String title;
private int priceCode;
public Movie(String title, int priceCode) {
this.title = title;
this.priceCode = priceCode;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public int getPriceCode() {
return priceCode;
}
public void setPriceCode(int priceCode) {
this.priceCode = priceCode;
}
}
public class Rental {
private Movie movie;
private int dayRented;
public Movie getMovie() {
return movie;
}
public int getDayRented() {
return dayRented;
}
public Rental(Movie movie, int dayRented) {
this.movie = movie;
this.dayRented = dayRented;
}
}
测试类:用于每次重构后,确认没有错误,影响原有的功能;
import org.junit.Assert;
public class TestOutput {
@org.junit.Test
public void Should_get_correct_statement() {
// 三部影片
Movie movie1 = new Movie("xxxxx", Movie.NEW_RELEASE);
Movie movie2 = new Movie("AAAAA", Movie.CHILDREN);
Movie movie3 = new Movie("DDDDD", Movie.REGULAR);
//两名顾客
Customer customer1 = new Customer("H1212");
Customer customer2 = new Customer("J1212");
//顾客租约
Rental rental1 = new Rental(movie1, 10);
Rental rental2 = new Rental(movie2, 1);
Rental rental3 = new Rental(movie3, 3);
customer1.addRental(rental1);
customer1.addRental(rental2);
customer2.addRental(rental2);
customer2.addRental(rental3);
Assert.assertEquals(customer1.statement(), "Rental Records for H1212\n" +
"\txxxxx\t30.0\n" +
"\tAAAAA\t1.5\n" +
"Amount owed is 31.5\n" +
"You earned 3 frequent renter points");
Assert.assertEquals(customer2.statement(), "Rental Records for J1212\n" +
"\tAAAAA\t1.5\n" +
"\tDDDDD\t3.5\n" +
"Amount owed is 5.0\n" +
"You earned 2 frequent renter points");
}
}
Customer提供了 statement 方法来输出租赁清单信息 ;
如果你发现自己需要为程序添加一个特性,而代码结构使你无法方便达成目的,那就重构那个程序,使特性的添加比较容易进行,然后再添加特性。
重构的第一步就是要为即将修改的代码建立一组可靠的测试环境,这些测试是必要的,避免因为重构带来新的 bug。
测试类已经在上面写好了。
重构之前,首先检查自己是否有一套可靠的测试机制,这些测试必须有自我检验的能力。
其中我们发现statement 方法(Long Method),需要分解长函数,把较小的代码移至更合适的地方。重构 statement 可以分以下几个步骤:
注意两个临时变量,thisAmount 和 each,需要处理;
任何一个傻瓜都能写出计算机可以理解的代码,唯有写出人类容易理解的代码,才是优秀的程序员。
提炼amountFor函数:
import java.util.Enumeration;
import java.util.Vector;
public class Customer {
private String name;
private Vector rentals = new Vector<>();
public Customer(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void addRental(Rental rental) {
rentals.add(rental);
}
public String statement() {
double totalAmount = 0;
int frequentRenterPointers = 0;
Enumeration rentalEnumeration = rentals.elements();
String result = "Rental Records for " + getName() + "\n";
while (rentalEnumeration.hasMoreElements()) {
double thisAmount = 0;
Rental each = rentalEnumeration.nextElement();
thisAmount = amountFor(each);
// add frequent renter points
frequentRenterPointers++;
// add bonus for two day new release rental
if ((each.getMovie().getPriceCode() == Movie.NEW_RELEASE) && each.getDayRented() > 1) {
frequentRenterPointers++;
}
//show figures for this rental
result += "\t" + each.getMovie().getTitle() + "\t" + thisAmount + "\n";
totalAmount += thisAmount;
}
// add footer lines
result += "Amount owed is " + totalAmount + "\n";
result += "You earned " + frequentRenterPointers + " frequent renter points";
return result;
}
private double amountFor(Rental each) {
double thisAmount = 0;
switch (each.getMovie().getPriceCode()) {
case Movie.REGULAR:
thisAmount += 2;
if (each.getDayRented() > 2) {
thisAmount += (each.getDayRented() - 2) * 1.5;
}
break;
case Movie.NEW_RELEASE:
thisAmount += each.getDayRented() * 3;
break;
case Movie.CHILDREN:
thisAmount += 1.5;
if (each.getDayRented() > 3) {
thisAmount += (each.getDayRented() - 3) * 1.5;
}
break;
}
return thisAmount;
}
}
import java.util.Enumeration;
import java.util.Vector;
public class Customer {
private String name;
private Vector rentals = new Vector<>();
public Customer(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void addRental(Rental rental) {
rentals.add(rental);
}
public String statement() {
double totalAmount = 0;
int frequentRenterPointers = 0;
Enumeration rentalEnumeration = rentals.elements();
String result = "Rental Records for " + getName() + "\n";
while (rentalEnumeration.hasMoreElements()) {
double thisAmount = 0;
Rental each = rentalEnumeration.nextElement();
thisAmount = amountFor(each);
// add frequent renter points
frequentRenterPointers++;
// add bonus for two day new release rental
if ((each.getMovie().getPriceCode() == Movie.NEW_RELEASE) && each.getDayRented() > 1) {
frequentRenterPointers++;
}
//show figures for this rental
result += "\t" + each.getMovie().getTitle() + "\t" + thisAmount + "\n";
totalAmount += thisAmount;
}
// add footer lines
result += "Amount owed is " + totalAmount + "\n";
result += "You earned " + frequentRenterPointers + " frequent renter points";
return result;
}
private double amountFor(Rental aRental) {
double result = 0;
switch (aRental.getMovie().getPriceCode()) {
case Movie.REGULAR:
result += 2;
if (aRental.getDayRented() > 2) {
result += (aRental.getDayRented() - 2) * 1.5;
}
break;
case Movie.NEW_RELEASE:
result += aRental.getDayRented() * 3;
break;
case Movie.CHILDREN:
result += 1.5;
if (aRental.getDayRented() > 3) {
result += (aRental.getDayRented() - 3) * 1.5;
}
break;
}
return result;
}
}
import java.util.Enumeration;
import java.util.Vector;
public class Customer {
private String name;
private Vector rentals = new Vector<>();
public Customer(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void addRental(Rental rental) {
rentals.add(rental);
}
public String statement() {
double totalAmount = 0;
int frequentRenterPointers = 0;
Enumeration rentalEnumeration = rentals.elements();
String result = "Rental Records for " + getName() + "\n";
while (rentalEnumeration.hasMoreElements()) {
double thisAmount = 0;
Rental each = rentalEnumeration.nextElement();
thisAmount = amountFor(each);
// add frequent renter points
frequentRenterPointers++;
// add bonus for two day new release rental
if ((each.getMovie().getPriceCode() == Movie.NEW_RELEASE) && each.getDayRented() > 1) {
frequentRenterPointers++;
}
//show figures for this rental
result += "\t" + each.getMovie().getTitle() + "\t" + thisAmount + "\n";
totalAmount += thisAmount;
}
// add footer lines
result += "Amount owed is " + totalAmount + "\n";
result += "You earned " + frequentRenterPointers + " frequent renter points";
return result;
}
private double amountFor(Rental aRental) {
return aRental.getCharge();
}
}
public class Rental {
private Movie movie;
private int dayRented;
public Movie getMovie() {
return movie;
}
public int getDayRented() {
return dayRented;
}
public Rental(Movie movie, int dayRented) {
this.movie = movie;
this.dayRented = dayRented;
}
public double getCharge() {
double result = 0;
switch (getMovie().getPriceCode()) {
case Movie.REGULAR:
result += 2;
if (getDayRented() > 2) {
result += (getDayRented() - 2) * 1.5;
}
break;
case Movie.NEW_RELEASE:
result += getDayRented() * 3;
break;
case Movie.CHILDREN:
result += 1.5;
if (getDayRented() > 3) {
result += (getDayRented() - 3) * 1.5;
}
break;
}
return result;
}
}
import java.util.Enumeration;
import java.util.Vector;
public class Customer {
private String name;
private Vector rentals = new Vector<>();
public Customer(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void addRental(Rental rental) {
rentals.add(rental);
}
public String statement() {
double totalAmount = 0;
int frequentRenterPointers = 0;
Enumeration rentalEnumeration = rentals.elements();
String result = "Rental Records for " + getName() + "\n";
while (rentalEnumeration.hasMoreElements()) {
double thisAmount = 0;
Rental each = rentalEnumeration.nextElement();
thisAmount = each.getCharge();
// add frequent renter points
frequentRenterPointers++;
// add bonus for two day new release rental
if ((each.getMovie().getPriceCode() == Movie.NEW_RELEASE) && each.getDayRented() > 1) {
frequentRenterPointers++;
}
//show figures for this rental
result += "\t" + each.getMovie().getTitle() + "\t" + thisAmount + "\n";
totalAmount += thisAmount;
}
// add footer lines
result += "Amount owed is " + totalAmount + "\n";
result += "You earned " + frequentRenterPointers + " frequent renter points";
return result;
}
}
删除无用的临时变量, thisAmount 只是接受了一下 getCharge(),后面没有作任何变化,
可以采用以查询替换变量原则去除变量 thisAmount,暂时先不讨论两次计算带来的性能开销,这个完全可以优化掉。
import java.util.Enumeration;
import java.util.Vector;
public class Customer {
private String name;
private Vector rentals = new Vector<>();
public Customer(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void addRental(Rental rental) {
rentals.add(rental);
}
public String statement() {
double totalAmount = 0;
int frequentRenterPointers = 0;
Enumeration rentalEnumeration = rentals.elements();
String result = "Rental Records for " + getName() + "\n";
while (rentalEnumeration.hasMoreElements()) {
Rental each = rentalEnumeration.nextElement();
// add frequent renter points
frequentRenterPointers++;
// add bonus for two day new release rental
if ((each.getMovie().getPriceCode() == Movie.NEW_RELEASE) && each.getDayRented() > 1) {
frequentRenterPointers++;
}
//show figures for this rental
result += "\t" + each.getMovie().getTitle() + "\t" + each.getCharge() + "\n";
totalAmount += each.getCharge();
}
// add footer lines
result += "Amount owed is " + totalAmount + "\n";
result += "You earned " + frequentRenterPointers + " frequent renter points";
return result;
}
}
提炼完成函数后,放入Rental类中;
import java.util.Enumeration;
import java.util.Vector;
public class Customer {
private String name;
private Vector rentals = new Vector<>();
public Customer(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void addRental(Rental rental) {
rentals.add(rental);
}
public String statement() {
double totalAmount = 0;
int frequentRenterPointers = 0;
Enumeration rentalEnumeration = rentals.elements();
String result = "Rental Records for " + getName() + "\n";
while (rentalEnumeration.hasMoreElements()) {
Rental each = rentalEnumeration.nextElement();
frequentRenterPointers += each.getFrequentRenterPoints();
//show figures for this rental
result += "\t" + each.getMovie().getTitle() + "\t" + each.getCharge() + "\n";
totalAmount += each.getCharge();
}
// add footer lines
result += "Amount owed is " + totalAmount + "\n";
result += "You earned " + frequentRenterPointers + " frequent renter points";
return result;
}
}
public class Rental {
private Movie movie;
private int dayRented;
public Movie getMovie() {
return movie;
}
public int getDayRented() {
return dayRented;
}
public Rental(Movie movie, int dayRented) {
this.movie = movie;
this.dayRented = dayRented;
}
public double getCharge() {
double result = 0;
switch (getMovie().getPriceCode()) {
case Movie.REGULAR:
result += 2;
if (getDayRented() > 2) {
result += (getDayRented() - 2) * 1.5;
}
break;
case Movie.NEW_RELEASE:
result += getDayRented() * 3;
break;
case Movie.CHILDREN:
result += 1.5;
if (getDayRented() > 3) {
result += (getDayRented() - 3) * 1.5;
}
break;
}
return result;
}
int getFrequentRenterPoints() {
if ((getMovie().getPriceCode() == Movie.NEW_RELEASE) && getDayRented() > 1) {
return 2;
}
return 1;//不喜欢用else
}
}
去除totalAmount和frequentRenterPointers 临时变量:
经过上述重构之后,发现临时变量依旧是一个灾难,就需要再使用用查询代替临时变量(Replace Temp With Query)的手法来清理这两个变量:
totalAmount:
import java.util.Enumeration;
import java.util.Vector;
public class Customer {
private String name;
private Vector rentals = new Vector<>();
public Customer(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void addRental(Rental rental) {
rentals.add(rental);
}
public String statement() {
int frequentRenterPointers = 0;
Enumeration rentalEnumeration = rentals.elements();
String result = "Rental Records for " + getName() + "\n";
while (rentalEnumeration.hasMoreElements()) {
Rental each = rentalEnumeration.nextElement();
frequentRenterPointers += each.getFrequentRenterPoints();
//show figures for this rental
result += "\t" + each.getMovie().getTitle() + "\t" + each.getCharge() + "\n";
}
// add footer lines
result += "Amount owed is " + getTotalCharge() + "\n";
result += "You earned " + frequentRenterPointers + " frequent renter points";
return result;
}
private double getTotalCharge() {
double result = 0;
Enumeration rentalList = rentals.elements();
while (rentalList.hasMoreElements()) {
Rental each = (Rental) rentalList.nextElement();
result += each.getCharge();
}
return result;
}
}
frequentRenterPointers:
import java.util.Enumeration;
import java.util.Vector;
public class Customer {
private String name;
private Vector rentals = new Vector<>();
public Customer(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void addRental(Rental rental) {
rentals.add(rental);
}
public String statement() {
Enumeration rentalEnumeration = rentals.elements();
String result = "Rental Records for " + getName() + "\n";
while (rentalEnumeration.hasMoreElements()) {
Rental each = rentalEnumeration.nextElement();
//show figures for this rental
result += "\t" + each.getMovie().getTitle() + "\t" + each.getCharge() + "\n";
}
// add footer lines
result += "Amount owed is " + getTotalCharge() + "\n";
result += "You earned " + getFrequentRenterPointers() + " frequent renter points";
return result;
}
private double getTotalCharge() {
double result = 0;
Enumeration rentalList = rentals.elements();
while (rentalList.hasMoreElements()) {
Rental each = (Rental) rentalList.nextElement();
result += each.getCharge();
}
return result;
}
private int getFrequentRenterPointers() {
int result = 0;
Enumeration rentalList = rentals.elements();
while (rentalList.hasMoreElements()) {
Rental each = (Rental) rentalList.nextElement();
result += each.getFrequentRenterPoints();
}
return result;
}
}
switch应该放在正确位置:根据movie选择,所以放到movie中
最好不要在另一个对象的属性基础上运用Switch语句,如果不得不用,也应该咋对象自己的数据上使用,而不是别人的身上;
public class Movie {
public static final int REGULAR = 0;
public static final int NEW_RELEASE = 1;
public static final int CHILDREN = 2;
private String title;
private int priceCode;
public Movie(String title, int priceCode) {
this.title = title;
this.priceCode = priceCode;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public int getPriceCode() {
return priceCode;
}
public void setPriceCode(int priceCode) {
this.priceCode = priceCode;
}
public double getCharge(int dayRented) {
double result = 0;
switch (getPriceCode()) {
case Movie.REGULAR:
result += 2;
if (dayRented > 2) {
result += (dayRented - 2) * 1.5;
}
break;
case Movie.NEW_RELEASE:
result += dayRented * 3;
break;
case Movie.CHILDREN:
result += 1.5;
if (dayRented > 3) {
result += (dayRented - 3) * 1.5;
}
break;
}
return result;
}
public int getFrequentRenterPoints(int dayRented) {
if ((getPriceCode() == Movie.NEW_RELEASE) && dayRented > 1) {
return 2;
}
return 1;//不喜欢用else
}
}
public class Rental {
private Movie movie;
private int dayRented;
public Movie getMovie() {
return movie;
}
public int getDayRented() {
return dayRented;
}
public Rental(Movie movie, int dayRented) {
this.movie = movie;
this.dayRented = dayRented;
}
public double getCharge() {
return movie.getCharge(dayRented);
}
int getFrequentRenterPoints() {
return movie.getFrequentRenterPoints(dayRented);
}
}
public class Movie {
public static final int REGULAR = 0;
public static final int NEW_RELEASE = 1;
public static final int CHILDREN = 2;
private String title;
private int priceCode;
private Price price;
public Movie(String title, int priceCode) {
this.title = title;
setPriceCode(priceCode);
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public int getPriceCode() {
return price.getPriceCode();
}
public void setPriceCode(int priceCode) {
switch (priceCode) {
case REGULAR:
price = new RegularPrice();
break;
case CHILDREN:
price = new ChildrensPrice();
break;
case NEW_RELEASE:
price = new NewReleasePrice();
break;
default:
throw new IllegalArgumentException("Incorrect price code");
}
}
public double getCharge(int dayRented) {
double result = 0;
switch (getPriceCode()) {
case Movie.REGULAR:
result += 2;
if (dayRented > 2) {
result += (dayRented - 2) * 1.5;
}
break;
case Movie.NEW_RELEASE:
result += dayRented * 3;
break;
case Movie.CHILDREN:
result += 1.5;
if (dayRented > 3) {
result += (dayRented - 3) * 1.5;
}
break;
}
return result;
}
public int getFrequentRenterPoints(int dayRented) {
if ((getPriceCode() == Movie.NEW_RELEASE) && dayRented > 1) {
return 2;
}
return 1;//不喜欢用else
}
}
public abstract class Price {
abstract int getPriceCode();
}
class ChildrensPrice extends Price {
@Override
int getPriceCode() {
return Movie.CHILDREN;
}
}
class NewReleasePrice extends Price {
@Override
int getPriceCode() {
return Movie.NEW_RELEASE;
}
}
class RegularPrice extends Price {
@Override
int getPriceCode() {
return Movie.REGULAR;
}
}
public abstract class Price {
abstract int getPriceCode();
abstract double getCharge(int dayRenter);
}
class ChildrensPrice extends Price {
@Override
int getPriceCode() {
return Movie.CHILDREN;
}
@Override
double getCharge(int dayRented) {
double result = 1.5;
if (dayRented > 3) {
result += (dayRented - 3) * 1.5;
}
return result;
}
}
class NewReleasePrice extends Price {
@Override
int getPriceCode() {
return Movie.NEW_RELEASE;
}
@Override
double getCharge(int dayRented) {
return dayRented * 3;
}
}
class RegularPrice extends Price {
@Override
int getPriceCode() {
return Movie.REGULAR;
}
@Override
double getCharge(int dayRented) {
double result = 2;
if (dayRented > 2) {
result += (dayRented - 2) * 1.5;
}
return result;
}
}
import org.junit.Before;
public class Movie {
public static final int REGULAR = 0;
public static final int NEW_RELEASE = 1;
public static final int CHILDREN = 2;
private String title;
private int priceCode;
private Price price;
public Movie(String title, int priceCode) {
this.title = title;
setPriceCode(priceCode);
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public int getPriceCode() {
return price.getPriceCode();
}
public void setPriceCode(int priceCode) {
switch (priceCode) {
case REGULAR:
price = new RegularPrice();
break;
case CHILDREN:
price = new ChildrensPrice();
break;
case NEW_RELEASE:
price = new NewReleasePrice();
break;
default:
throw new IllegalArgumentException("Incorrect price code");
}
}
public double getCharge(int dayRented) {
return price.getCharge(dayRented);
}
public int getFrequentRenterPoints(int dayRented) {
if ((getPriceCode() == Movie.NEW_RELEASE) && dayRented > 1) {
return 2;
}
return 1;//不喜欢用else
}
}
public abstract class Price {
abstract int getPriceCode();
abstract double getCharge(int dayRenter);
public int getFrequentRenterPoints(int dayRented) {
return 1;
}
}
class ChildrensPrice extends Price {
@Override
int getPriceCode() {
return Movie.CHILDREN;
}
@Override
double getCharge(int dayRented) {
double result = 1.5;
if (dayRented > 3) {
result += (dayRented - 3) * 1.5;
}
return result;
}
}
class NewReleasePrice extends Price {
@Override
int getPriceCode() {
return Movie.NEW_RELEASE;
}
@Override
double getCharge(int dayRented) {
return dayRented * 3;
}
public int getFrequentRenterPoints(int dayRented) {
return (dayRented > 1) ? 2 : 1;
}
}
class RegularPrice extends Price {
@Override
int getPriceCode() {
return Movie.REGULAR;
}
@Override
double getCharge(int dayRented) {
double result = 2;
if (dayRented > 2) {
result += (dayRented - 2) * 1.5;
}
return result;
}
}
import org.junit.Before;
public class Movie {
public static final int REGULAR = 0;
public static final int NEW_RELEASE = 1;
public static final int CHILDREN = 2;
private String title;
private int priceCode;
private Price price;
public Movie(String title, int priceCode) {
this.title = title;
setPriceCode(priceCode);
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public int getPriceCode() {
return price.getPriceCode();
}
public void setPriceCode(int priceCode) {
switch (priceCode) {
case REGULAR:
price = new RegularPrice();
break;
case CHILDREN:
price = new ChildrensPrice();
break;
case NEW_RELEASE:
price = new NewReleasePrice();
break;
default:
throw new IllegalArgumentException("Incorrect price code");
}
}
public double getCharge(int dayRented) {
return price.getCharge(dayRented);
}
public int getFrequentRenterPoints(int dayRented) {
return price.getFrequentRenterPoints(dayRented);
}
}