auto commit
This commit is contained in:
141
notes/重构.md
141
notes/重构.md
@ -123,24 +123,22 @@
|
||||
|
||||
影片出租店应用程序,需要计算每位顾客的消费金额。
|
||||
|
||||
包括三个类:Movie、Rental 和 Customer,Rental 包含租赁的 Movie 以及天数。
|
||||
包括三个类:Movie、Rental 和 Customer。
|
||||
|
||||
<div align="center"> <img src="../pics//c2f0c8e2-da66-498c-a38f-e1176abee29e.png"/> </div><br>
|
||||
|
||||
最开始的实现是把所有的计费代码都放在 Customer 类中。
|
||||
|
||||
可以发现,该代码没有使用 Customer 类中的任何信息,更多的是使用 Rental 类的信息,因此第一个可以重构的点就是把具体计费的代码移到 Rental 类中,然后 Customer 类的 getTotalCharge() 方法只需要调用 Rental 类中的计费方法即可。
|
||||
最开始的实现是把所有的计费代码都放在 Customer 类中。可以发现,该代码没有使用 Customer 类中的任何信息,更多的是使用 Rental 类的信息,因此第一个可以重构的点就是把具体计费的代码移到 Rental 类中,然后 Customer 类的 getTotalCharge() 方法只需要调用 Rental 类中的计费方法即可。
|
||||
|
||||
```java
|
||||
public class Customer {
|
||||
class Customer {
|
||||
|
||||
private List<Rental> rentals = new ArrayList<>();
|
||||
|
||||
public void addRental(Rental rental) {
|
||||
void addRental(Rental rental) {
|
||||
rentals.add(rental);
|
||||
}
|
||||
|
||||
public double getTotalCharge() {
|
||||
double getTotalCharge() {
|
||||
double totalCharge = 0.0;
|
||||
for (Rental rental : rentals) {
|
||||
switch (rental.getMovie().getMovieType()) {
|
||||
@ -151,7 +149,6 @@ public class Customer {
|
||||
totalCharge += rental.getDaysRented() * 2;
|
||||
break;
|
||||
case Movie.Type3:
|
||||
totalCharge += 1.5;
|
||||
totalCharge += rental.getDaysRented() * 3;
|
||||
break;
|
||||
}
|
||||
@ -159,42 +156,41 @@ public class Customer {
|
||||
return totalCharge;
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
```java
|
||||
public class Rental {
|
||||
class Rental {
|
||||
private int daysRented;
|
||||
|
||||
private Movie movie;
|
||||
|
||||
public Rental(int daysRented, Movie movie) {
|
||||
Rental(int daysRented, Movie movie) {
|
||||
this.daysRented = daysRented;
|
||||
this.movie = movie;
|
||||
}
|
||||
|
||||
public Movie getMovie() {
|
||||
Movie getMovie() {
|
||||
return movie;
|
||||
}
|
||||
|
||||
public int getDaysRented() {
|
||||
int getDaysRented() {
|
||||
return daysRented;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```java
|
||||
public class Movie {
|
||||
class Movie {
|
||||
|
||||
public static final int Type1 = 0, Type2 = 1, Type3 = 2;
|
||||
static final int Type1 = 0, Type2 = 1, Type3 = 2;
|
||||
|
||||
private int type;
|
||||
|
||||
public Movie(int type) {
|
||||
Movie(int type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public int getMovieType() {
|
||||
int getMovieType() {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
@ -223,9 +219,9 @@ public class App {
|
||||
|
||||
<div align="center"> <img src="../pics//41026c79-dfc1-40f7-85ae-062910fd272b.png"/> </div><br>
|
||||
|
||||
但是我们需要允许一部影片可以在运行过程中改变其所属的分类,但是上述的继承方案却不可行,因为一个对象所属的类在编译过程就确定了。
|
||||
有一条设计原则指示应该多用组合少用继承,这是因为组合比继承具有更高的灵活性。例如上面的继承方案,一部电影要改变它的计费方式,就要改变它所属的类,但是对象所属的类在编译时期就确定了,无法在运行过程中改变。(运行时多态可以在运行过程中改变一个父类引用指向的子类对象,但是无法改变一个对象所属的类。)
|
||||
|
||||
为了解决上述的问题,需要使用策略模式。引入 Price 类,它有多种实现。Movie 组合了一个 Price 对象,并且在运行时可以改变组合的 Price 对象,从而使得它的计费方式发生改变。
|
||||
策略模式就是使用组合替代继承的一种解决方案。引入 Price 类,它有多种实现。Movie 组合了一个 Price 对象,并且在运行时可以改变组合的 Price 对象,从而使得它的计费方式发生改变。
|
||||
|
||||
<div align="center"> <img src="../pics//8c0b3ae1-1087-46f4-8637-8d46b4ae659c.png"/> </div><br>
|
||||
|
||||
@ -235,6 +231,107 @@ public class App {
|
||||
|
||||
<div align="center"> <img src="../pics//3ca58a41-8794-49c1-992e-de5d579a50d1.png"/> </div><br>
|
||||
|
||||
重构后的代码:
|
||||
|
||||
```java
|
||||
class Customer {
|
||||
private List<Rental> rentals = new ArrayList<>();
|
||||
|
||||
void addRental(Rental rental) {
|
||||
rentals.add(rental);
|
||||
}
|
||||
|
||||
double getTotalCharge() {
|
||||
double totalCharge = 0.0;
|
||||
for (Rental rental : rentals) {
|
||||
totalCharge += rental.getCharge();
|
||||
}
|
||||
return totalCharge;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```java
|
||||
class Rental {
|
||||
private int daysRented;
|
||||
|
||||
private Movie movie;
|
||||
|
||||
Rental(int daysRented, Movie movie) {
|
||||
this.daysRented = daysRented;
|
||||
this.movie = movie;
|
||||
}
|
||||
|
||||
double getCharge() {
|
||||
return daysRented * movie.getCharge();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```java
|
||||
interface Price {
|
||||
double getCharge();
|
||||
}
|
||||
```
|
||||
|
||||
```java
|
||||
class Price1 implements Price {
|
||||
@Override
|
||||
public double getCharge() {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```java
|
||||
class Price2 implements Price {
|
||||
@Override
|
||||
public double getCharge() {
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```java
|
||||
package imp2;
|
||||
|
||||
class Price3 implements Price {
|
||||
@Override
|
||||
public double getCharge() {
|
||||
return 3;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```java
|
||||
class Movie {
|
||||
|
||||
private Price price;
|
||||
|
||||
Movie(Price price) {
|
||||
this.price = price;
|
||||
}
|
||||
|
||||
double getCharge() {
|
||||
return price.getCharge();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```java
|
||||
class App {
|
||||
|
||||
public static void main(String[] args) {
|
||||
Customer customer = new Customer();
|
||||
Rental rental1 = new Rental(1, new Movie(new Price1()));
|
||||
Rental rental2 = new Rental(2, new Movie(new Price2()));
|
||||
customer.addRental(rental1);
|
||||
customer.addRental(rental2);
|
||||
System.out.println(customer.getTotalCharge());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
# 二、重构原则
|
||||
|
||||
## 定义
|
||||
@ -265,9 +362,7 @@ public class App {
|
||||
|
||||
## 修改接口
|
||||
|
||||
如果重构手法改变了已发布的接口,就必须维护新旧两个接口。
|
||||
|
||||
可以保留旧接口,让旧接口去调用新接口,并且使用 Java 提供的 @deprecation 将旧接口标记为弃用。
|
||||
如果重构手法改变了已发布的接口,就必须维护新旧两个接口。可以保留旧接口,让旧接口去调用新接口,并且使用 Java 提供的 @deprecation 将旧接口标记为弃用。
|
||||
|
||||
可见修改接口特别麻烦,因此除非真有必要,否则不要发布接口,并且不要过早发布接口。
|
||||
|
||||
@ -467,7 +562,7 @@ Extract Method 会把很多参数和临时变量都当做参数,可以用 Repl
|
||||
|
||||
Java 可以使用 Junit 进行单元测试。
|
||||
|
||||
测试应该能够完全自动化,并能检查测试的结果。Junit 可以做到。
|
||||
测试应该能够完全自动化,并能检查测试的结果。
|
||||
|
||||
小步修改,频繁测试。
|
||||
|
||||
|
Reference in New Issue
Block a user