auto commit
This commit is contained in:
parent
4e0646dad6
commit
9164742ff0
|
@ -3,7 +3,7 @@
|
|||
* [二、用名字表达代码含义](#二用名字表达代码含义)
|
||||
* [三、名字不能带来歧义](#三名字不能带来歧义)
|
||||
* [四、良好的代码风格](#四良好的代码风格)
|
||||
* [五、编写注释](#五编写注释)
|
||||
* [五、为何编写注释](#五为何编写注释)
|
||||
* [六、如何编写注释](#六如何编写注释)
|
||||
* [七、提高控制流的可读性](#七提高控制流的可读性)
|
||||
* [八、拆分长表达式](#八拆分长表达式)
|
||||
|
@ -43,12 +43,14 @@
|
|||
|
||||
起完名字要思考一下别人会对这个名字有何解读,会不会误解了原本想表达的含义。
|
||||
|
||||
用 min、max 表示数量范围;用 first、last 表示访问空间的包含范围,begin、end 表示访问空间的排除范围,即 end 不包含尾部。
|
||||
布尔相关的命名加上 is、can、should、has 等前缀。
|
||||
|
||||
- 用 min、max 表示数量范围;
|
||||
- 用 first、last 表示访问空间的包含范围;
|
||||
- begin、end 表示访问空间的排除范围,即 end 不包含尾部。
|
||||
|
||||
<div align="center"> <img src="../pics//05907ab4-42c5-4b5e-9388-6617f6c97bea.jpg"/> </div><br>
|
||||
|
||||
布尔相关的命名加上 is、can、should、has 等前缀。
|
||||
|
||||
# 四、良好的代码风格
|
||||
|
||||
适当的空行和缩进。
|
||||
|
@ -61,11 +63,9 @@ int b = 11; // 注释
|
|||
int c = 111; // 注释
|
||||
```
|
||||
|
||||
语句顺序不能随意,比如与 html 表单相关联的变量的赋值应该和表单在 html 中的顺序一致;
|
||||
语句顺序不能随意,比如与 html 表单相关联的变量的赋值应该和表单在 html 中的顺序一致。
|
||||
|
||||
把相关的代码按块组织起来放在一起。
|
||||
|
||||
# 五、编写注释
|
||||
# 五、为何编写注释
|
||||
|
||||
阅读代码首先会注意到注释,如果注释没太大作用,那么就会浪费代码阅读的时间。那些能直接看出含义的代码不需要写注释,特别是并不需要为每个方法都加上注释,比如那些简单的 getter 和 setter 方法,为这些方法写注释反而让代码可读性更差。
|
||||
|
||||
|
@ -109,14 +109,6 @@ int add(int x, int y) {
|
|||
}
|
||||
```
|
||||
|
||||
在很复杂的函数调用中对每个参数标上名字:
|
||||
|
||||
```java
|
||||
int a = 1;
|
||||
int b = 2;
|
||||
int num = add(\* x = *\ a, \* y = *\ b);
|
||||
```
|
||||
|
||||
使用专业名词来缩短概念上的解释,比如用设计模式名来说明代码。
|
||||
|
||||
# 七、提高控制流的可读性
|
||||
|
@ -128,16 +120,6 @@ if (len < 10)
|
|||
if (10 > len)
|
||||
```
|
||||
|
||||
if / else 条件语句,逻辑的处理顺序为:① 正逻辑;② 关键逻辑;③ 简单逻辑。
|
||||
|
||||
```java
|
||||
if (a == b) {
|
||||
// 正逻辑
|
||||
} else{
|
||||
// 反逻辑
|
||||
}
|
||||
```
|
||||
|
||||
只有在逻辑简单的情况下使用 ? : 三目运算符来使代码更紧凑,否则应该拆分成 if / else;
|
||||
|
||||
do / while 的条件放在后面,不够简单明了,并且会有一些迷惑的地方,最好使用 while 来代替。
|
||||
|
|
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 可以做到。
|
||||
测试应该能够完全自动化,并能检查测试的结果。
|
||||
|
||||
小步修改,频繁测试。
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user