重构——重新组织数据

it2024-03-27  58

Self Encapsulate Field(自封装字段)

简介访问变量的好处:子类可以通过覆写一个函数而改变获取数据的途径;还支持更灵活的数据管理方式,如延迟初始化。

直接访问变量的好处:代码比较容易阅读;

如果你想访问超类中的一个字段,却又想在子类中将对这个变量的访问改为一个计算后的值,这时候就应该进行“字段自我封装”。

值对象和引用对象之间的相互转换

值对象和引用对象

如果引用对象开始变得难以使用,也许就应该将它改为值对象,引用对象必须被某种方式控制,你总是必须向其控制者请求适当的引用对象。它们可能造成内存区域之间错综复杂的关联。在分布式系统和并发系统中,不可变的值对象特别有用,因为无需开率它们的同步问题。

值对象有一个非常重要的特征:它们应该是不可变的,无论何时,只要你调用同一对象的同一个查询函数,都应该得到同样的结果。如果保证了这一点,就可以方式以多个对象表示同一事物。如果值对象是可变的,你必须确保对某一对象的修改会自动更新其他代表相同的事物。

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

你有一个数组,其中的元素各自代表不同的东西,以对象替换数组,对于数组中的每一个元素,以一个字段来表示。

动机

数组是一种常见的用以组织数据的结构,不过,它们应该只用于“以某种顺序容纳一组相似对象”。有时候数组容纳多种不同对象会给用户带来麻烦。

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

类之中有一个数值类型码,但它并不影响类的行为,以一个新的类替换该数值类型码。

如果带着一个有意义的符号名,类型码的可读性还不错,问题在于,符号名终究只是个别名,编译器看见的、进行类型校验的,还是背后的数值。任何加收类型码作为参数的函数,所期望的实际上是一个数值,无法强制使用符号名。这会大大降低代码的可读性。

以血型来划分人

public 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; } public int getBloodGroup() { return bloodGroup; } public void setBloodGroup(int bloodGroup) { this.bloodGroup = bloodGroup; } } 为类型码建立一个类

这个类需要一个用以记录类型码的字段,其类型应该和类型码相同,并且有对应的取值函数。此外,还应该用一组静态变量保存允许被创建的实例,并以一个静态函数根据原本的类型码返回合适的实例。

public 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(2); private static final BloodGroup[] _value = {O,A,B,AB}; private final int _code; public BloodGroup(int _code) { this._code = _code; } public static BloodGroup get_value(int arg) { return _value[arg]; } public int get_code() { return _code; } } 修改源类实现,让它使用上述新建的类; public class Person { public static final int O = BloodGroup.O.get_code(); public static final int A = BloodGroup.A.get_code(); public static final int B = BloodGroup.B.get_code(); public static final int AB = BloodGroup.AB.get_code(); private int bloodGroup; public Person(int bloodGroup) { this.bloodGroup = bloodGroup; } public int getBloodGroup() { return bloodGroup; } public void setBloodGroup(int bloodGroup) { this.bloodGroup = bloodGroup; } } 对于源类中每一个使用类型码的函数,相应建立一个函数,让新函数使用新建的类。删除使用类型码的旧接口,并删除保存就类型码的静态变量。 public class Person { private BloodGroup bloodGroup; public Person(BloodGroup bloodGroup) { this.bloodGroup = bloodGroup; } public BloodGroup getBloodGroup() { return bloodGroup; } public void setBloodGroup(BloodGroup bloodGroup) { this.bloodGroup = bloodGroup; } }

创建Person对象

Person person = new Person(BloodGroup.O); int code = person.getBloodGroup().get_code(); person.setBloodGroup(BloodGroup.AB);

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

你有一个不可变类型码,它会影响类的行为

动机

如果你面对的类型码不会影响宿主类的行为,可以使用以类替换类型码的方法来处理它们。但是如果类型码会影响宿主类的行为,那么最好的办法就是借助多态来处理变化行为。

一般来说,这种情况的标志就像switch这样的条件表达式,这种条件表达式可能有两种表现形式:switch语句和if-then-else结构。它们都是检查类型码值,并根据不同的值执行不同的动作。这种情况应该使用以多态替换条件进行重构,但是在这之前,首先应该将类型码替换为可拥有多态行为的继承体系,这样的一个继承体系应该以类型码的宿主类为基类,并针对每一种类型码各建立一个子类。

以类型码的宿主类为基本,针对每种类型码建立相应子类;

但是以下两种情况不能这么做:

类型码值在对象创建之后发生了改变;类型码宿主类已经有了子类;

这时就应该使用以state/strategy来替换类型码。

使用以子类取代类型码的原因:

为以多态替换条件搭建舞台;一旦宿主类中出现了“只与具备特定类型码之对象相关”的特性。完成本项重构之后可以使用Push Down Method和Push Down Field将这些特性推到合适的子类去。它把对不同行为的了解从类用户转移到类自身,如果需要再加入新的行为变化,只需添加一个子类就行了。

案例:

public class Employee { private int _type; static final int ENGINEER = 0; static final int SALESMAN = 1; static final int MANAGER = 2; Employee (int type){ _type = type; } } 使用Self Encapsulate Fireld将类型码自我封装起来。

如果类型码被传递给构造函数,就需要将构造函数换成工厂函数;

public class Employee { private int _type; static final int ENGINEER = 0; static final int SALESMAN = 1; static final int MANAGER = 2; // 替换构造函数为工厂函数 static Employee create(int type){ return new Employee(type); } private Employee (int type){ _type = type; } // 实现自我封装 public int get_type() { return _type; } } 为类型码的每一个数值建立一个相应的子类,在每个子类中覆写类型码的取值函数,使其返回相应的类型码值; class Engineer extends Employee{ private Engineer(int type) { super(type); } int getType(){ return Employee.ENGINEER; } } 从超类中删除保存类型码的字段,将类型码访问函数声明为抽象函数。 public abstract class Employee { private int _type; static final int ENGINEER = 0; static final int SALESMAN = 1; static final int MANAGER = 2; abstract int get_type(); static Employee create(int type){ switch (type){ case ENGINEER: return new Engineer(); case SALESMAN: return new Salesman(); case MANAGER: return new Manager(); default: throw new IllegalArgumentException("Incorrect type code value"); } } } public class Engineer extends Employee{ @Override int get_type() { return ENGINEER; } }

Replace Type Code with State/Strategy (以State/Strategy取代类型码)

你有一个类型码,它会影响类的行为,但是无法通过继承收发消除。

动机

如果类型码的值在对象声明期中发生改变或宿主类不能被继承,可以使用本重构。

模式选择

如果打算在完成本项重构之后再以多态取代条件表达式简化一个算法,那么Strategy比较合适;如果打算搬移与状态相关的数据,而且把新建对象视为一种状态变迁,就应该选择使用State模式。

案例

public class Employee01 { private int _type; static final int ENGINEER = 0; static final int SALESMAN = 1; static final int MANAGER = 2; Employee01(int type){ _type = type; } private int monthlySalary; private int commission; private int bonus; public int getMonthlySalary() { return monthlySalary; } public void setMonthlySalary(int monthlySalary) { this.monthlySalary = monthlySalary; } public int getCommission() { return commission; } public void setCommission(int commission) { this.commission = commission; } public int getBonus() { return bonus; } public void setBonus(int bonus) { this.bonus = bonus; } int payAmount(){ switch (_type){ case ENGINEER: return monthlySalary; case SALESMAN: return monthlySalary+commission; case MANAGER: return monthlySalary+bonus; default: throw new RuntimeException("Incorrect Employee"); } } } 自我封装 public class Employee01 { private int _type; static final int ENGINEER = 0; static final int SALESMAN = 1; static final int MANAGER = 2; Employee01(int type){ set_type(type); } private int monthlySalary; private int commission; private int bonus; public int getMonthlySalary() { return monthlySalary; } public void setMonthlySalary(int monthlySalary) { this.monthlySalary = monthlySalary; } public int getCommission() { return commission; } public void setCommission(int commission) { this.commission = commission; } public int getBonus() { return bonus; } public void setBonus(int bonus) { this.bonus = bonus; } void set_type(int arg){ _type = arg; } int get_type(){ return _type; } int payAmount(){ switch (get_type()){ case ENGINEER: return monthlySalary; case SALESMAN: return monthlySalary+commission; case MANAGER: return monthlySalary+bonus; default: throw new RuntimeException("Incorrect Employee"); } } } 新建一个类,根据类型码的用途为它命名,这是一个状态对象。 public abstract class EmloyeeType { abstract int getTypeCode(); } 为这个新类添加子类,每个子类对应一种类型码。 public class Engineer extends EmployeeType { @Override int getTypeCode() { return Employee01.ENGINEER; } } public class Salesman extends EmployeeType { @Override int getTypeCode() { return Employee01.SALESMAN; } } public class Manager extends EmployeeType { @Override int getTypeCode() { return Employee01.MANAGER; } } 在超类中建立一个抽象的查询函数,用以返回类型码,在每个子类中覆写该函数,返回确切的类型码。在源类中建立一个字段,用以保存新建的状态对象; public class Employee01 { private EmployeeType _type; static final int ENGINEER = 0; static final int SALESMAN = 1; static final int MANAGER = 2; Employee01(int type){ set_type(type); } void set_type(int arg){ switch (arg){ case ENGINEER: _type = new Engineer(); break; case SALESMAN: _type = new Salesman(); break; case MANAGER: _type =new Manager(); break; default: throw new IllegalArgumentException("Incorrect type code value"); } } EmployeeType get_type(){ return _type; } int payAmount(){ switch (get_type().getTypeCode()){ case ENGINEER: return monthlySalary; case SALESMAN: return monthlySalary+commission; case MANAGER: return monthlySalary+bonus; default: throw new RuntimeException("Incorrect Employee"); } } } 在调整源类中负责查询类型码的函数,将查询动作转发给状态对象。 public class Employee01 { private EmployeeType _type; Employee01(int type){ set_type(type); } private int monthlySalary; private int commission; private int bonus; public int getMonthlySalary() { return monthlySalary; } public void setMonthlySalary(int monthlySalary) { this.monthlySalary = monthlySalary; } public int getCommission() { return commission; } public void setCommission(int commission) { this.commission = commission; } public int getBonus() { return bonus; } public void setBonus(int bonus) { this.bonus = bonus; } void set_type(int arg){ _type = EmployeeType.newType(arg); } EmployeeType get_type(){ return _type; } int payAmount(){ switch (get_type().getTypeCode()){ case EmployeeType.ENGINEER: return monthlySalary; case EmployeeType.SALESMAN: return monthlySalary+commission; case EmployeeType.MANAGER: return monthlySalary+bonus; default: throw new RuntimeException("Incorrect Employee"); } } } public abstract class EmployeeType { static final int ENGINEER = 0; static final int SALESMAN = 1; static final int MANAGER = 2; public static EmployeeType newType(int arg) { switch (arg){ case ENGINEER: return new Engineer(); case SALESMAN: return new Salesman(); case MANAGER: return new Manager(); default: throw new IllegalArgumentException("Incorrect type code value"); } } abstract int getTypeCode(); } 调整源类中为类型码设值得那个函数,将一个恰当的状态对象赋值给保存状态对象的那个字段。

Replace Conditional with Polymorphism(以多态取代条件表达式)

你手上有多个条件表示,它根据对象类型的不同而选择不同的行为,将这个条件表达式的每个分支放进一个子类内的覆写函数中,然后将原始函数声明为抽象函数。

动机

多态的最根本好处就是:如果你需要根据对象的不同类型而采取不同行为,多态使你不必编写明显的条件表达式。

多态能够给你带来很多好处,如果同一组条件表达式在程序许多地点出现,那么使用多态的收益是最大的。使用条件表达式时,如果你想添加一种新类型,就必须查找并更新所有条件表达式。但如果改用多态,只需建立一个新的子类,并在其中提供适当的函数就行。类的用户不需要了解这个子类,这就大大降低了系统各部分之间的依赖,使系统升级更加容易。

做法

在使用本项重构之前要先建立继承结构,即Replace Type Code with State/Strategy或Replace Type Code with Subclasses;

你的目标可能是switch语句,也可能是if语句:

如果要处理的条件表达式是一个更大函数中的一部分,首先对条件表达式进行分析,使用提炼函数将它提炼到一个独立的函数中。 public abstract class EmployeeType { int payAmount(Employee01 emp){ switch (getTypeCode()){ case EmployeeType.ENGINEER: return emp.getMonthlySalary(); case EmployeeType.SALESMAN: return emp.getMonthlySalary()+emp.getCommission(); case EmployeeType.MANAGER: return emp.getMonthlySalary()+emp.getBonus(); default: throw new RuntimeException("Incorrect Employee"); } } } public class Employee01 { int payAmount(){ return _type.payAmount(this); } } 任选一个子类,在其中建立一个函数,是指覆写超类中容纳条件表达式的那个函数。将与该子类相关的条件表达式分支复制到新建函数中,并对它做适当调整。 public class Engineer extends EmployeeType { @Override int getTypeCode() { return ENGINEER; } @Override int payAmount(Employee01 emp) { return emp.getMonthlySalary(); } }

将超类的payment()函数声明为抽象函数:

abstract int payAmount(Employee01 emp);
最新回复(0)