《重构-改善既有代码的设计》系列读书笔记(四、重新组织数据)

重新组织数据

Self Encapsulate Field(自封装字段)

你直接访问一个字段,但与字段之间的耦合关系逐渐变得笨拙。

动机
  • 间接访问的好处是子类可以通过覆写一个函数而改变获取数据的途径,支持懒加载。
  • 直接访问的好处是代码更容易阅读。

Replace Data Value with Object(以对象取代数据值)

你有一个数据项,需要与其他数据和行为一起使用才有意义。

动机

当几个相关联的数据同时出现而且经常使用时,将它们组合为一个新的数据类吧。

做法
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
//before
class Order...
public Order(String customer) {
_customer = customer;
}
public String getCustomer() {
return _customer;
}
public void setCustomer(String arg) {
_customer = arg;
}
private String _customer;
//after
class Customer {
public Customer(String name) {
_name = name;
}
public String getName() {
return _name;
}
private final String _name;
}
class Order...
public Order(String customerName) {
_customer = new Customer(customerName);
}
public String getCustomer() {
return _customer.getName();
}
public void setCustomer(String customerName) {
_customer = new Customer(customerName);
}
private Customer _customer;

Change Value to Reference(将值对象改为引用对象)

  • 值对象不能被修改,引用对象可以被修改
  • 值对象每次获取到的值应该相等,而引用对象每次获取到的值不一定相等
  • 使用单例或者工厂创建引用对象

Change Reference to Value(将引用对象改为值对象)

  • 跟上面的过程刚好相反

Replace Array with Object(以对象取代数组)

动机
  • 一个数组中存放了不同类型的数据,这种骚操作有点要不得啊。。。
做法
  • 这个做法很有步步重构的代表性,所以写详细点。
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
80
//before
String[] row = new String[3];
row[0] = "Livepool";
row[1] = "15";
String name = row[0];
int wins = Integer.parseInt(row[1]);
// step 1 新建类
class Performance {
public String[] _data = new String[3];
}
Performance row = new Performance();
row._date[0] = "Livepool";
row._date[1] = "15";
String name = row._date[0];
int wins = Integer.parseInt(row._date[1]);
// step 2 封装替换存取方法
class Performance {
public String[] _data = new String[3];
public String getName() {
return _date[0];
}
public void setName(String arg) {
_date[0] = arg;
}
public int getWins() {
return Integer.parseInt(_data[1]);
}
public void setWins(String arg) {
_data[1] = arg
}
}
Performance row = new Performance();
row.setName = "Livepool";
row.setWins = "15";
String name = row.getName();
int wins = row.getWins();
// step 3 添加属性 删除临时数据
class Performance {
private String _name;
private String _wins;
public String getName() {
return _name;
}
public void setName(String arg) {
_name = arg;
}
public int getWins() {
return Integer.parseInt(_wins);
}
public void setWins(String arg) {
_wins = arg
}
}
Performance row = new Performance();
row.setName = "Livepool";
row.setWins = "15";
String name = row.getName();
int wins = row.getWins();

Duplicate Observed Data(复制被监视数据)

  • mvc模式的应用,数据处理和UI层分离
  • 使用观察者或者通知的方式进行数据的同步

Change Unidirectional Association to Bidirectional(将单向关联改为双向关联)

两个类都需要使用对方的特性,但其间只有一条单向链接。

动机
  • 当两个类需要互相引用时,可以使用反向指针来实现。
  • 如果使用不当,反向指针很容易造成混乱,但只要你习惯了这种手法,其实并不太复杂。
  • 双向依赖容易产生僵尸对象,造成内存泄露。
做法
  • 不建议这么做。。。

Change Bidirectional Association to Unidirectional(将双向关联改为单向关联)

动机
  • 双向依赖导致其中一个类的修改都可能影响另外一个类,形成了高度耦合的代码,并导致重新编译的问题。
  • 将双向依赖改为单向依赖的理由比较充分,打破相互依赖可以避免可能的内存泄露问题。
做法
  • 如果另外一条依赖是完全多余的,则直接去掉。
  • 如果不能去掉,可以采用协议的方式去打破其中一条依赖。
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
//before
ClassA {
ClassB b = new ClassB();
public void funcA() {
b.funcB();
}
}
ClassB {
ClassA a = new ClassA();
public void funcB() {
a.funcA();
}
}
//after
ClassA_Delegate {
public void delegate_func();
}
ClassA {
ClassA_Delegate delegate;
public void funcA() {
delegate.delegate_func()
}
}
ClassB<ClassA_Delegate> {
ClassA a = new ClassA();
a.delegate = self;
public void funcB() {
a.funcA();
}
public void delegate_func() {
self.funcB()
}
}
// 这个例子比较特殊,两个函数会相互调用产生爆栈,加代理并不能消除互相调用的逻辑,只能消除相互引用。

Replace Magic Number with Symbolic Constant(以字面常量取代魔法数)

  • 道理大家都懂,真正写起来就控制不住自己的手啊。。。

Encapsulate Field(封装字段)

你的类存在一个public字段

动机
  • 限制外界直接修改类的字段
做法
  • 将字段改为private,然后添加存/取方法。

Encapsulate Collection(封装集合)

动机 & 做法
  • 不要直接返回集合本身,这样集合很容易被修改。
  • 应该只返回集合的一个只读副本,并提供集合元素的添加和删除方法。
  • 如果是多线程访问,这种方式还能扩展为线程安全的访问方式。

Replace Type Code with Class(以类取代类型码)

Replace Type Code with Subclasses(以子类取代类型码)

Replace Type Code with State/Strategy(以策略模式取代类型码)

Replace Subclass with Fields(以字段取代子类)

动机
  • 如果子类中只有常数函数,就没有足够的存在价值。
做法
  • 你可以在父类中定义一个相应的字段,在类的构造时进行赋值,然后直接返回该字段的值即可。
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
//before
abstract class Person {
abstract boolean isMale();
abstract char getCode();
}
class Male extends Person {
boolean isMale() {
return true;
}
char getCode() {
return 'M';
}
}
class Female extends Person {
boolean isMale() {
return false;
}
char getCode() {
return 'F';
}
}
//after
abstract class Person {
private final boolean _isMale;
private final char _code;
protected Person (boolean isMale, char code) {
_isMale = isMale;
_code = code;
}
static Person createMale() {
return new Person(true,'M');
}
static Person createFemale() {
return new Person(false,'F');
}
boolean isMale() {
return _isMale;
}
char getCode() {
return _code;
}
}