将函数本体替换为另一种算法。
第七章 在对象之间搬移特性
============================================================================
1、搬移函数
你的程序中,有个函数与其所在类之外的另一个类进行更多的交流,在该函数最常引用的类中建立一个有着类似行为的新函数。将旧函数变成一个单纯的委托关系,或是将旧函数完全移除。
“搬移函数”是重构理论的支柱。如果一个类有太多行为,或如果一个类与另一个类有太多合作而形成高度耦合,我就会搬移函数。通过这种手段,可以使系统中的类更简单,这些类最终也将更干净利落地实现系统交付的任务。
2、搬移字段
3、提炼类
某个类做了应该由两个类做的事,建立一个新类,将相关的字段和函数从旧类搬移至新类。
4、将类内联化
某个类没有做太多的事情,将这个类的所有特性搬移至另一个类中,然后移除该类。
5、隐藏委托关系
客户端通过一个委托类来调用另一个对象时,在服务类上建立客户所需的所有函数,用以隐藏委托关系。
public Person getManager(){
return _department.getManager();
}
只有完成了对Department所有函数的委托关系,并相应修改了Person的所有客户,我就可以移除Person中的访问函数getDepartment()了。
6、移除中间人
某个类做了过多的简单委托动作时,让客户直接调用受委托类。
7、引入外加函数
Date start = new Date(previous.getYear(),previous.getMonth(),previous.getDate()+1);
变为
Date start = nextDay(previous);
private static Date nextDay(previous.getYear(),previous.getMonth(),previous.getDate()+1);
貌似懂了一点。
外加函数终归是权宜之计,如果有可能,仍然应该讲这些函数搬移至它们应该在的地方。
8、引入本地扩展
你需要为服务类提供一些额外函数,但你无法修改这个类,建立一个新类,使它包含这些额外函数,让这个扩展类成为原类的子类或包装类。
「动机」
在子类和包装类之间做选择时,我通常首选子类,因为这样的工作量比较少。制作子类的最大障碍在于,如果原数据是可修改的,一个修改动作无法改变两个副本,这时就必须改用包装类
「做法」
-
建立一个扩展类,将它作为原始类的子类或包装类。
-
在扩展类中加入转型构造函数,所谓转型构造函数,是指“接受原对象作为参数”的构造函数。如果采用子类化方案,那么转型构造函数应该调用适当的超类构造函数;如果采用包装类方案,那么转型构造函数应该将它得到的传入参数以实例变量的形式保存起来,用作接受委托的原对象。
-
在扩展类中加入新特性。
-
根据需要,将原对象改为扩展对象。
-
将针对原始类定义的所有外加函数搬移至扩展类中。
第八章 重新组织数据
=========================================================================
1、自封装字段
为字段建立取值/设置函数,并且只以这些函数来访问字段。
2、以对象取代数据值
你有一个数据项,需要与其它数据和行为一起使用才有意义,将数据项变成对象。
3、将值对象改为引用对象
4、将引用对象改为值对象
你有一个引用对象,很小且不可变,而且不易管理,此时,将它变成一个值对象。
要在引用对象和值对象之间做出选择,有时并不容易。做出选择后,你常会需要一条回头路。
如果引用对象开始变得难以使用,也许就应该将它变为值对象。引用对象必须被某种方式控制,你总是必须向其控制者请求适当的引用对象。它们可能造成内存区域之间错综复杂的关联。在分布式系统和并发系统中,不可变的值对象特别有用,因为你无需考虑它们的同步问题。
5、以对象取代数组
以对象取代数组,对于数组中的每一个元素,以一个字段来表示。
「动机」
数组是一种常见的用以组织数据的结构。不过,它们应该只用于“以某种顺序容纳一组相似对象”。有时你会发现,一个数组容纳了多种不同对象,这会给用户带来麻烦,因为它们很难记住像“数组的第一个元素是人名”这样的约定。对象就不同了,你可以运用字段名称和函数名称来传达这样的信息,也无需使用注释。而且使用对象,你还可以将信息封装起来,并使用“搬移函数”将它加上相关行为。
我之前做过一个数据上传系统,有这么个结构:
Map<String,String[]> hashMap = new HashMap<String,String[]>();
String[] data = new Data[4];
data[0] = “1”;//状态state
data[1] = “1”;//延展状态exstate
data[2] = “1”;//数值
data[3] = “2020-12-21 23:22:00”;//数据生成时间
String key = “01A01”;//测点编号
hashMap.put(key,data);
…
for(String key : hashMap.keySet){
//状态state、延展状态exstate、数值、数据生成时间
String[] data = hashMap.get(key);
String state = data[0];
String exstate = data[1];
String value = data[2];
String time = data[3]’
}
因为数据存在基本数据、实时数据、分钟累计数据,这种结构的代码项目中比比皆是,写的那叫一个烂,这个上传程序折磨的我苦不堪言,往事不堪回首。
「做法」
@Data
public class BasicData(){
private int state;
private int exstate;
private double value;
private Date time;
}
Map<String,BasicData> hashMap = new HashMap<String,BasicData>();
BasicData basicData = new BasicData();
basicData.setState = “1”;
basicData.setExstate = “1”;
basicData.setValue = 3.12;
String str = “2020-12-21 23:22:00”;
basicData.setTime = DateUtil.StringToDate(str,“yyyy-MM-dd hh:mm:ss”);
String key = “01A01”;//测点编号
hashMap.put(key,basicData);
…
for(String key : hashMap.keySet){
//状态state、延展状态exstate、数值、数据生成时间
BasicData basicData = hashMap.get(key);
String state = basicData.getState();
String exstate = basicData.getExstate();
double value = basicData.getValue();
Date time = basicData.getTime();
}
多么痛的领悟。
其实很简单。
/**
@startTime 2020-12-22 21:30
@endTime 2020-12-22 22:50
@startPage 189
@endPage 235
@efficiency 235/7 = 33.6页/天
@needDays 412/33.6 = 12天
@overDay 2020-12-16 + 12天 = 2020-12-27
*/
6、复制“被监视数据”
你有一些领域数据置身于GUI控件中,而领域函数需要访问这些数据。将该数据复制到一个领域对象中。建立一个Observer模式,用以同步领域对象和GUI对象内的重复数据。
「动机」
一个分层良好的系统,应该讲处理用户界面和处理业务逻辑的代码分开。之所以这样做,原因有以下几点:
-
你可能需要使用不同的用户界面来表现相同的业务逻辑,如果同时承担两种责任,用户界面会变得过分复杂;
-
与GUI隔离之后,领域对象的维护和演化都会变得更容易,你甚至可以让不同的开发者负责不同部分的开发。
7、将单向关联改为双向关联
两个类需要使用对方特性,但其间只有一条单向连接。
添加一个反向指针,并使修改函数能够同时更新两条连接。
8、将双向关联改为单向关联
去掉不必要的关联。
9、以字面常量取代魔法数
你有一个字面数值,带有特殊的含义。创造一个常量,根据其意义为它命名,并将上述的字面数值替换为这个常量。
10、封装字段
将字段封装为private,并提供相应的访问函数。
11、封装集合
12、以数据类取代记录
记录型结构是许多编程环境的共同性质,你可能处理一些遗留程序,也可能需要通过传统API与记录结构交流,或者是从数据读出的记录。这些时候你就有必要创建一个接口类,用以处理这些外来数据。将这些数据搬移到Java的bean中。