摘要:重构入门案例
概述
示例:影片出租店应用程序,计算每一位顾客的消费金额并打印详单。操作者告诉程序:顾客租客哪些影片、租期多长,程序便根据租赁时间和影片类型计算出费用。
影片分为三类:普通片、儿童片、新片。除了计算费用还要为常客计算积分,积分会根据足片种类是否为新片而有不同。
Ver01 示例-简单逻辑思维,没有扩展
- 使用几个类表达上述
- Movie(影片类):数据类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24public class Movie {
public static final int CHILDRENS = 2; //儿童
public static final int REGULAR = 0; //常客
public static final int NEW_RELEASE = 1;// 新片
private String _title;
private int _priceCode;
public Movie(String title, int priceCode) {
_title = title;
_priceCode = priceCode;
}
public int getPriceCode() {
return _priceCode;
}
public String getTitle() {
return _title;
}
public void setPriceCode(int priceCode) {
_priceCode = priceCode;
}
} - Rental(租赁类) 表示某个顾客租了一部影片
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17public class Rental {
private Movie _movie; // 影片
private int _daysRented; // 租期
public Rental(Movie movie, int daysRented) {
_movie = movie;
_daysRented = daysRented;
}
public int getDaysRented() {
return _daysRented;
}
public Movie getMovie() {
return _movie;
}
} - Customer(顾客) 数据以及相应的访问函数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58public class Customer {
private String _name; // 姓名
private Vector _rentals = new Vector(); // 租借记
public Customer(String name) {
_name = name;
};
public void addRental(Rental arg) {
_rentals.addElement(arg);
}
public String getName() {
return _name;
}
public String statement() {
double totalAmount = 0; // 总消费金。
int frequentRenterPoints = 0; // 常客积点
Enumeration rentals = _rentals.elements();
String result = "Rental Record for " + getName() + "\n";
while (rentals.hasMoreElements()) {
double thisAmount = 0;
Rental each = (Rental) rentals.nextElement(); // 取得一笔租借记。
// determine amounts for each line
switch (each.getMovie().getPriceCode()) { // 取得影片出租价格
case Movie.REGULAR: // 普通片
thisAmount += 2;
if (each.getDaysRented() > 2)
thisAmount += (each.getDaysRented() - 2) * 1.5;
break;
case Movie.NEW_RELEASE: // 新片
thisAmount += each.getDaysRented() * 3;
break;
case Movie.CHILDRENS: // 儿童。
thisAmount += 1.5;
if (each.getDaysRented() > 3)
thisAmount += (each.getDaysRented() - 3) * 1.5;
break;
}
// add frequent renter points (累计常客积点。
frequentRenterPoints++;
// add bonus for a two day new release rental
if ((each.getMovie().getPriceCode() == Movie.NEW_RELEASE)
&& each.getDaysRented() > 1)
frequentRenterPoints++;
// show figures for this rental(显示此笔租借记录)
result += "\t" + each.getMovie().getTitle() + "\t"
+ String.valueOf(thisAmount) + "\n";
totalAmount += thisAmount;
}
// add footer lines(结尾打印)
result += "Amount owed is " + String.valueOf(totalAmount) + "\n";
result += "You earned " + String.valueOf(frequentRenterPoints)
+ " frequent renter points";
return result;
}
} - 单元测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14public class CustomerTest {
public void statement() {
Customer customer = new Customer("John");
String title = "Titanic";
int priceCode = 2;
int _daysRented = 7;
Movie movie = new Movie(title, priceCode);
Rental rental = new Rental(movie, _daysRented);
customer.addRental(rental);
String result = customer.statement();
System.out.println(result);
}
}
- Movie(影片类):数据类
- 说明
单一逻辑,没有扩展性以及面向对象思维
Ver02 分解重组statement()
- 避免长函数、逻辑独立的抽成方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63public class Customer {
private String _name; // 姓名
private Vector _rentals = new Vector(); // 租借记
public Customer(String name) {
_name = name;
};
public void addRental(Rental arg) {
_rentals.addElement(arg);
}
public String getName() {
return _name;
}
public String statement() {
double totalAmount = 0; // 总消费金。
int frequentRenterPoints = 0; // 常客积点
Enumeration rentals = _rentals.elements();
String result = "Rental Record for " + getName() + "\n";
while (rentals.hasMoreElements()) {
Rental each = (Rental) rentals.nextElement(); // 取得一笔租借记。
double thisAmount = amountFor(each);
// add frequent renter points (累计常客积点。
frequentRenterPoints++;
// add bonus for a two day new release rental
if ((each.getMovie().getPriceCode() == Movie.NEW_RELEASE)
&& each.getDaysRented() > 1)
frequentRenterPoints++;
// show figures for this rental(显示此笔租借记录)
result += "\t" + each.getMovie().getTitle() + "\t"
+ String.valueOf(thisAmount) + "\n";
totalAmount += thisAmount;
}
// add footer lines(结尾打印)
result += "Amount owed is " + String.valueOf(totalAmount) + "\n";
result += "You earned " + String.valueOf(frequentRenterPoints)
+ " frequent renter points";
return result;
}
private double amountFor(Rental rental) {
double thisAmount = 0;
// determine amounts for each line
switch (rental.getMovie().getPriceCode()) { // 取得影片出租价格
case Movie.REGULAR: // 普通片
thisAmount += 2;
if (rental.getDaysRented() > 2)
thisAmount += (rental.getDaysRented() - 2) * 1.5;
break;
case Movie.NEW_RELEASE: // 新片
thisAmount += rental.getDaysRented() * 3;
break;
case Movie.CHILDRENS: // 儿童。
thisAmount += 1.5;
if (rental.getDaysRented() > 3)
thisAmount += (rental.getDaysRented() - 3) * 1.5;
break;
}
return thisAmount;
}
}
Ver03 金额计算代码,迁移至Rental类
参看:github ver03
Ver04 同类聚合,单一原则
Movie中放置与影片直接相关方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80public class Movie {
public static final int CHILDRENS = 2;
public static final int REGULAR = 0;
public static final int NEW_RELEASE = 1;
private String _title;
private int _priceCode;
public Movie(String title, int priceCode) {
_title = title;
_priceCode = priceCode;
}
public int getPriceCode() {
return _priceCode;
}
public String getTitle() {
return _title;
}
public void setPriceCode(int priceCode) {
_priceCode = priceCode;
}
public double getCharge(int daysRented) {
double result = 0;
switch (getPriceCode()) {
case Movie.REGULAR:
result += 2;
if (daysRented > 2)
result += (daysRented - 2) * 1.5;
break;
case Movie.NEW_RELEASE:
result += daysRented * 3;
break;
case Movie.CHILDRENS:
result += 1.5;
if (daysRented > 3)
result += (daysRented - 3) * 1.5;
break;
}
return result;
}
public int getFrequentRenterPoints(int daysRented) {
if ((getPriceCode() == Movie.NEW_RELEASE) && daysRented > 1)
return 2;
else
return 1;
}
}
```
2. 租赁放置相关方法
```java
public class Rental {
private Movie _movie; // 影片
private int _daysRented; // 租期
public Rental(Movie movie, int daysRented) {
_movie = movie;
_daysRented = daysRented;
}
public int getDaysRented() {
return _daysRented;
}
public Movie getMovie() {
return _movie;
}
double getCharge() {
return _movie.getCharge(_daysRented);
}
public int getFrequentRenterPoints() {
return _movie.getFrequentRenterPoints(_daysRented);
}
}顾客相关
public class Customer { private String _name; // 姓名 private Vector _rentals = new Vector(); // 租借记 public Customer(String name) { _name = name; }; public void addRental(Rental arg) { _rentals.addElement(arg); } public String getName() { return _name; } public String statement() { int frequentRenterPoints = 0; Enumeration rentals = _rentals.elements(); String result = "Rental Record for " + getName() + "\n"; while (rentals.hasMoreElements()) { Rental each = (Rental) rentals.nextElement(); frequentRenterPoints += each.getFrequentRenterPoints(); // show figures for this rental result += "\t" + each.getMovie().getTitle() + "\t" + String.valueOf(each.getCharge()) + "\n"; } // add footer lines result += "Amount owed is " + String.valueOf(getTotalCharge()) + "\n"; result += "You earned " + String.valueOf(getTotalFrequentRenterPoints()) + " frequent renter points"; return result; } public String htmlStatement() { Enumeration rentals = _rentals.elements(); String result = "<H1>Rentals for <EM>" + getName() + "</EM></ H1><P>\n"; while (rentals.hasMoreElements()) { Rental each = (Rental) rentals.nextElement(); // show figures for each rental result += each.getMovie().getTitle() + ": " + String.valueOf(each.getCharge()) + "<BR>\n"; } // add footer lines result += "<P>You owe <EM>" + String.valueOf(getTotalCharge()) + "</EM><P>\n"; result += "On this rental you earned <EM>" + String.valueOf(getTotalFrequentRenterPoints()) + "</EM> frequent renter points<P>"; return result; } // 译注:此即所谓query method private int getTotalFrequentRenterPoints() { int result = 0; Enumeration rentals = _rentals.elements(); while (rentals.hasMoreElements()) { Rental each = (Rental) rentals.nextElement(); result += each.getFrequentRenterPoints(); } return result; } // 译注:此即所谓query method private double getTotalCharge() { double result = 0; Enumeration rentals = _rentals.elements(); while (rentals.hasMoreElements()) { Rental each = (Rental) rentals.nextElement(); result += each.getCharge(); } return result; } }
重新分析,抽象模型
抽象过程一、
- 影片实体:抽象类Movie,子类:普通片、儿童片、新片.每个子类有各自计费方法实现。常见操作是抽象功能接口
影片可能会根据不同的类型有不同的收费规则,所以影片可以根据类型有不同的子类,但是有个问题,影片如果换了类型,但是class是不能换的,所以可以考虑影片没有子类,但是影片包含一个Price的类,这个类可以根据不同类型有不同子类。Movie有一个Price类的引用,不同的类型对应不同的Price子类,调用Price类的方法来计算收费。
参看:github ver03
- 影片实体:抽象类Movie,子类:普通片、儿童片、新片.每个子类有各自计费方法实现。常见操作是抽象功能接口