##重新组织数据
8.1 Self Encapsulate Field (自封装字段)
你直接访问一个字段,但与字段之间的耦合关系逐渐变得笨拙。为这个字段建立取值/设值函数,并且只以这些函数来访问字段。
8.2 Replace Data Value with Object(以对象取代数据值)
你有一个数据项,需要与其他数据和行为一起使用采有意义,将数据项变成对象。
做法:
1. 为待替换数值新建一个类,在其中声明一个final字段,其类型和源类中的待替换数值类型一样。然后在新类中加入这个字段的取值函数,再加上一个接受此字段为参数的构造函数。
2. 将源类中的待替换数值字段的类型改为前面新建的类。
3. 修改源类中该字段的取值函数,令它们调用新类的取值函数。
4. 如果源类构造函数中用到这个待替换字段,我们就修改构造函数,令它改用新类的构造函数来对字段进行赋值动作。
5. 修改源类中待替换字段的设值函数,令它为新类创建一个实例。
6. 现在,你有可能需要对新类使用Change Value to Reference(8.3)。
class Order {
public Order(String customer){
this.customer = customer;
}
public String getCustomer(){
return customer;
}
public void setCustomer(String customer){
this.customer = customer;
}
private String customer;
}
to
class Customer{
public Customer(String name){
this.name = name;
}
public String getName(){
return name;
}
private final String name;
}
class Order {
public Order(String customer){
this.customer = new Customer(customer);
}
public String getCustomer(){
return customer.getName();
}
public void setCustomer(String customer){
this.customer = new Customer(customer);
}
private Customer customer;
}
8.3 Change Value to Reference(将值对象改为引用对象)
你从一个类衍生出许多彼此相等的实例,希望将它们替换为同一个对象。将这个值对象改成引用对象。
做法:
1. 使用Replace Constructor with Factory Method(10.12)。
2. 决定由什么对象负责提供访问新对象的途径。
使用静态字典或一个注册表对象;使用多个对象作为新对象的访问点。
3. 决定这些引用对象应该预先创建好,或是应该动态创建。
4. 修改工厂函数,令它们返回引用对象。
class Customer{
public Customer(String name){
this.name = name;
}
public String getName(){
return name;
}
private final String name;
}
class Order {
public Order(String customer){
this.customer = new Customer(customer);
}
public String getCustomer(){
return customer.getName();
}
public void setCustomer(String customer){
this.customer = new Customer(customer);
}
private Customer customer;
}
to
class Customer{
public static Customer create(String name){
return (Customer)instance.get(name);
}
private Customer(String name){
this.name = name;
}
private static Dictionary instance = new HashTable();
static void loadCustomer(){
new Customer("Lemon").store();
new Customer("Bilston").store();
}
private void store(){
instance.put(this.getName(),this);
}
}
class Order{
public Order(String customer){
this.customer = Customer.create(customer);
}
}
8.4 Change Reference to Value(将引用对象改为值对象)
你有一个引用对象,很小且不可变,而且不易管理。将它变成一个值对象。
做法:
1. 检查重构目标是否为不可变对象,或是否可以修改为不可变对象。
如果该对象目前还不是不可变的,就使用Remove Setting Method(10.10),直到它成为不可变的为止。
如果无法将该对象修改为不可变的,就放弃使用本项重构。
2. 建立equals()和hashCode()。
3. 考虑是否可以删除工厂函数,并将构造函数声明为public。
要把一个引用对象变成值对象,关键动作是:检查它是否可变。
8.5 Replace Array with Object(以对象取代数组)
你有一个数组,其中的元素各自代表不同的东西。以对象替换数组,对于数组中的每个元素,以一个字段来表示。
String[] row = new String[3];
row[0] = "Liverpool";
row[1] = "15";
to
Performance row = new Performance();
row.setName("Liverpool");
row.setWins("15");
8.7 Change Unidirectional Association to Bidirectional(将单向关联改为双向关联)
两个都需要使用对方特性,但其间只有一条单向连接。添加一个反向指针,并使修改函数能够同时更新两条连接。
动机:
在开发过程中,你发现被引用者需要得到其引用者以便进行某些操作。
做法:
1. 在被引用类中增加一个字段,用以保存反向指针。
2. 决定由哪个类-引用端还是被引用端-控制关联关系。
2.1 如果两者都是引用对象,而其间的关联是“一对多”,那么就由“拥有单一引用”的那一方承担“控制者”的角色。
2.2 如果某个对象是组成另一对象的部件,那么就由后者负责控制关联关系。
2.3 如果两者都是引用对象,而其间的关联是“多对多”关系,那么随便其中的哪个对象来控制关联关系都无所谓。
3. 在被控端建立一个辅助函数,其命名应该清楚指出它的有限用途。
4. 如果既有的修改函数在控制端,让它负责更新反向指针。
5. 如果既有的修改函数在被控端,就在控制端建立一个控制函数,并让既有的修改函数调用这个新建的控制函数。
class Order{
Customer getCustomer(){
return customer;
}
void setCustomer(Customer arg){
customer = arg;
}
Customer customer;
}
class Customer {
private Set orders = new HashSet();
}
to
//一对多的情况
class Customer{
//辅助函数
Set friendOrders(){
return orders;
}
//如果希望在Customer中也能修改连接,就调用控制函数
void addOrder(Order arg){
arg.setCustomer(this);
}
}
class Order{
void setCustomer(Customer arg){
//先让对方删除指向你的指针
if(customer != null)
customer.friendOrders().remove(this);
customer = arg;//再将你的指针指向新对象
//最后让新对象把它的指针指向你
if(customer != null)
customer.friendOrders().add(this);
}
}
//多对多
class Order{
void addCustomer(Customer arg){
arg.friendOrders().add(this);
customer.add(arg);
}
void removeCustomer(Customer arg){
arg.friendOrders().remove(this);
customer.remove(arg);
}
}
class Customer{
void addOrder(Order arg){
arg.addCustomer(this);
}
void removeOrder(Order arg){
arg.removeCustomer(this);
}
}
8.8 Change Bidirectional Association to Unidirectional(将双向关联改为单向关联)
两个类之间有双向关联,但其中一个类如今不再需要另一个类的特性,去除不必要的关联。
做法:
1. 找出保存“你想去除的指针”的字段,检查它的每一个用户,判断是否可以去除该指针。
2. 如果客户使用了取值函数,先运用Self Encapsulate Field(8.1)将待删除字段自我封装起来,然后处理取值函数,使它不再调用该字段。
3. 如果客户未使用取值函数,那就直接修改待删除字段的所有被引用点:改以其他途径获得该字段所保存的对象。
4. 如果已经没有任何函数使用待删除字段,移除所有对该字段的更新逻辑,然后移除该字段。
###这两种手法不太理解
8.11 Encapsulate Collection(封装集合)
有个函数返回一个集合,让这个函数返回该集合的一个只读副本,并在这个类中提供添加/移除集合元素的函数。
做法:
1. 加入为集合添加/移除元素的函数。
2. 将保存集合的字段初始化为一个空集合。
3. 找出集合设值函数的所有调用者。你可以修改那个设值函数,让它使用上述新建立的“添加/移除函数”,也可以直接修改调用端,改让它们调用上述新建立的“添加/移除”函数。
4. 找出所有“通过取值函数获得集合并修改其内容的”的函数。逐一修改这些函数,让他们改用“添加/移除”函数。
5. 修改完上述所有的“通过取值函数获得集合并修改集合内容”的函数后,修改取值函数自身,使它返回整个集合的只读副本。
6. 找出取值函数的所有用户,从中找出应该存在于集合所属对象内的代码。运用Extract Method(6.1)和Move Method(7.1)将这些代码移到宿主对象去。
7. 修改现有取值函数的名字,然后添加一个新取值函数,使其返回一个枚举。找出旧取值函数的所有被使用点,将它们都改为使用新取值函数。
8. 如果这一步跨度太大,可以先使用Rename Method修改原取值函数的名称,再建立一个新取值函数用以返回枚举。最后再修改所有调用者,使其调用新取值函数。
class Person{
public Set getCourses(){
return courses;
}
public void setCourses(Set arg){
courses = arg;
}
private Set courses;
}
to
class Person{
public void addCourse(Course arg){
courses.add(arg);
}
public void removeCourse(Course arg){
courses.remove(arg);
}
private Set courses = new HashSet();
//可以根据具体情况决定是否使用或移除设值函数。如果使用,设值函数可以调用添加函数初始化集合
public vodi setCourses(Set arg){
Iterator it = arg.iterator();
while(it.hasNext()){
addCourse((Course)it.next());
}
}
//对于通过取值函数修改集合的情况
public Set getCourses(){
return Collections.unmodifiableSet(courses);
}
}
###实际编码中,从来没见过,也从来没人提过集合的封装这个问题。
8.13 Replace Type Code with Class(以类取代类型码)
类之中有一个数值类型码,但它并不影响类的行为。以一个新的类替换该数值类型码。
class Person{
public static final int O = 0;
public static final int A = 1;
public static final int B = 2;
public static final int AB =3;
private int bloodGroup;
public Person(int bloodGroup) {
this.bloodGroup = bloodGroup;
}
//set/get方法
}
做法:
1、为类型码建立一个类。
这个类需要一个用以记录类型码的字段,其类型应该和类型码相同,并应该有对应的取值函数。此外还应该用一组静态变量保存运行被创建的实例,并以一个静态函数根据原本的类型码返回合适的实例。
class BloodGroup{
public static final BloodGroup O = new BloodGroup(0);
public static final BloodGroup A = new BloodGroup(1);
public static final BloodGroup B = new BloodGroup(2);
public static final BloodGroup AB = new BloodGroup(3);
public static final BloodGroup[] values = {O,A,B,AB};
private final int code;
private BloodGroup(int code){
this.code = code;
}
//get方法
public static BloodGroup code(int arg){
return values[arg];
}
}
2、修改源类实现,让它使用上述新建的类。
维持原先以类型码为基础的函数接口,但改变静态字段,以新建的类产生代码。然后,修改类型码相关函数,让它们也从新建的类中获取类型码。
class Person{
public static final int O = BloodGroup.O.getCode();
public static final int A = BloodGroup.A.getCode();
public static final int B = BloodGroup.B.getCode();
public static final int AB =BloodGroup.AB.getCode();
private BloodGroup bloodGroup;
public Person(int bloodGroup) {
this.bloodGroup = BloodGroup.code(bloodGroup);
}
public int getBloodGroup(){
return bloodGroup.getCode();
}
pubic void setBloodGroup(int arg){
this.bloodGroup = BloodGroup.code(arg);
}
}
3、对于源类中每一个使用类型码的函数,相应建立一个函数,让新函数使用新建的类。
你需要建立“以新类实例为自变量”的函数,用以替换原先“直接以类型码为参数”的函数。你还需要建立一个“返回新类实例”的函数,用以替换原先“直接返回类型码”的函数。建立新函数前,你可以使用Rename Method(10.1)修改原函数名称,明确指出哪些函数仍然使用旧式的类型码。
class Person{
public static final int O = BloodGroup.O.getCode();
public static final int A = BloodGroup.A.getCode();
public static final int B = BloodGroup.B.getCode();
public static final int AB =BloodGroup.AB.getCode();
private BloodGroup bloodGroup;
public Person(int bloodGroup) {
this.bloodGroup = BloodGroup.code(bloodGroup);
}
public int getBloodGroupCode(){
return bloodGroup.getCode();
}
pubic void setBloodGroup(int arg){
this.bloodGroup = BloodGroup.code(arg);
}
public BloodGroup getBloodGroup(){
return bloodGroup;
}
public Person(BloodGroup arg){
bloodGroup = arg;
}
public void setBloodGroup(BloodGroup arg){
bloodGroup = arg;
}
}
4、逐一修改源类用户,让它们使用新接口。
Person thePerson = new Person(Person.A);
thePerson.getBloodGroupCode();
thePerson.setBloodGroup(Person.AB);
to
Person thePerson = new Person(BloodGroup.A);
thePerson.getBloodGroup().getCode();
thePerson.setBloodGroup(BloodGroup.AB);
5、删除使用类型码的旧接口,并删除保存旧类型码的静态变量。
删掉与code相关的get/set方法和静态常量。
8.14 Replace Type Code with Subclasses(以子类取代类型码)
你有一个不可变的类型码,它会影响类的行为。以子类取代这个类型码。
不能使用这种方法的情况:
类型码值周对象创建之后发生了改变。
由于某些原因,类型码宿主类已经有了子类。
上述情况需要使用Replace Type Code with State/Strategy(8.15)。
class Employee {
private int type;
static final int ENGINEER = 0;
static final int SALESMAN = 1;
static final int MANAGER = 2;
Employee(int type) {
this.type = type;
}
}
做法:
1、使用Self Encapsulate Field(8.1)将类型码自我封装起来。
int getType() {
return type;
}
如果类型码被传递给构造函数,就需要将构造函数换成工厂函数。
static Employee create(int type) {
return new Employee(type);
}
private Employee(int type) {
this.type = type;
}
2、为类型码的 每一个数值建立一个相应的子类。在每个子类中覆写类型码的取值函数,使其返回相应的类型码值。
class Engineer extends Employee{
int getType(){
return Employee.ENGINEER;
}
}
class Employee{
static Employee create(int type) {
if(type == ENGINEER) return new Engineer();
else return new Employee(type);
}
}
3、从超类中删掉保存类型码的字段,将类型码访问函数声明为抽象函数。
abstract int getType();
static Employee create(int type) {
switch(type){
case ENGINEER:
return new Engineer();
case SALESMAN:
return new Salesman();
...
}
}
这时还可以使用Push Down Method(11.4)和Push Down Field(11.5),将只与特定种类雇员相关的函数和字段推到相关的 子类去。
8.15 Replace Type Code with State/Strategy(以State/Strategy取代类型码)
你有一个类型码,它会影响类的行为,但你无法通过继承手法消除它。以状态对象取代类型码。
如果你打算在完成本重构之后再以Replace Conditional with Polymorphism(9.6)简化一个算法,那么选择Strategy模式比较合适;如果你打算搬移与状态相关的数据,而且你把新建对象视为一种变迁状态,就应该选择使用State模式。
class Employee{
private int type;
private final int ENGINEER = 0;
private final int SALESMAN = 1;
private final int MANAGER = 2;
Employee(int type) {
this.type = type;
}
int payAmount(){
switch(type){
case ENGINEER:
return monthlySalary;
case SALESMAN:
return monthlySalary+commission;
case MANAGER:
return montyleSalary+bonus;
...
}
}
}
做法:
1、使用Self Encapsulate Field(8.1)将类型码自我封装起来。
加上对象的类型码可变,那么就不能使用继承方式来处理类型码。
Employee (int type) {
setType(type);
}
int getType(){
return type;
}
void setType(int type) {
this.type = type;
}
int payAmount(){
switch(getType()){
case ENGINEER:
return monthlySalary;
case SALESMAN:
return monthlySalary+commission;
case MANAGER:
return montyleSalary+bonus;
...
}
}
2、新建一个类,根据类型码的用途为它命名。这就是一个状态对象。
3、为这个新类添加子类,每个子类对应一种类型码。
4、在超类中建立一个抽象的查询函数,用以返回类型码。在每个子类中覆写该函数,返回确切的类型码。
abstract class EmployeeType{
abstract int getTypeCode();
}
class Engineer extends EmployeeType{
int getTypeCode(){
return Employee.ENGINEER;
}
}
//Manager类和Salesman类
...
5、在源类中建立一个字段,用以保存新建的状态对象。
6、调整源类中负责查询类型码的函数,将查询动作转发给状态对象。
7、调整源类中为类型码设值的函数,将一个恰当的状态对象子类赋值给“保存状态对象”的那个字段。
class Employee{
private EmployeeType type;
int getType(){
return type.getTypeCode();
}
void setType(int arg){
switch(arg) {
case ENGINEER:
type = new Engineer();
break;
case SALESMAN:
type = new Salesman();
break;
case MANAGER:
type = new Manager();
break;
...
}
}
}
之后可以运用Replace Constructor with Factory Method(10.12)针对不同的case子句建立相应的工厂函数。还可以再立刻使用Replace Conditional with Polymorphism(9.6),从而将其他的case消除掉。
可以将所有关于类型码和子类的知识都移到新类。
class Employee{
void setType(int arg){
type = EmployeeType.newType(arg);
}
}
class EmployeeType{
static EmployeeType newType(int code){
switch(code){
case ENGINEER:
return new Engineer();
case SALESMAN:
return new Salesman();
case MANAGER:
return new Manager();
...
}
}
static final int ENGINEER = 0;
static final int SALESMAN = 1;
static final int MANAGER = 2;
}
删掉原始类中的类型码定义,添加一个对新类的引用。
class Employee{
int payAmount(){
switch(getType()){
case EmployeeType.ENGINEER:
return monthlySalary;
case EmployeeType.SALESMAN:
return monthlySalary+commission;
...
}
}
}
最后,可以使用Replace Conditional with Polymorphism(9.6)处理payAmount()函数。