数据重构
数据应该符合封装的思想
是直接读取还是间接读取?
是用字段还是用对象?
是值对象还是引用对象?
是单向关联还是双向关联?
如何更好的表示类型码?常量?类?
Self Encapsulate Field(自封装值域)
problem:你直接访问一个私有值域(field),但与值域之间的耦合关系逐渐无法满足需求。
solution:为这个值域建立取值/设值函数(getting and setting methods ),并且只以这些函数来访问值域。
1 | private int _low, _high; |
重构原因?
「直接访问变量」的好处则是:代码比较容易阅读,不需要再多此一举。
「间接访问变量」更符合封装,好处是:
- 我可以在getter方式,或者setter方法中统一增加查询和修改的逻辑,减少耦合。
- 支持更灵活的数据管理方式:在字段getter和setter中可以轻松实现字段值的延迟初始化和验证。
- 可以在子类中覆盖getter和setter中和方法,实现自己的需求。
直接访问变量也并无坏处,一般可以根据公司的要求来,或者根据需要变化。
Replace Data Value with Object(以对象取代数据值)
problem:多个类中包含相同的一个数据字段,这个数据字段有相同的行为和关联数据。
solution:创建一个新类,把这个字段和他的行为放进去;original class保存这个新类。
重构原因?
该重构是Extract Class的特例。Extract Class是分离类的职责,而Replace Data Value with Object是解决“这个字段和行为系列可以同时出现在几个类中,从而创建重复的代码”的问题的。
如何重构?
- 使用Self Encapsulate Field隐藏original class中对于这个字段的访问
- 创建一个新类,copy字段和getter方法。创建一个构造器来初始化值。暂时不需要setter字段,因为目前只是重构成一个值对象。
- 在original class中,把字段的类型改成new class类型。
- 在original class的构造器中初始化这个字段。
- original class的getter调用associated object的getter
- original class的setter创建一个new class。
什么是值对象和引用对象?
值对象:一个现实对象对应多个对象。比如地址/颜色/喜好。用对象的关系来体现,引用对象就是组合关系。
引用对象:一个现实对象对应一个对象。比如客户/产品。用对象的关系来体现,引用对象就是聚合关系。
对于对象关系,请参考【UML】一文
Change Value to Reference(将实值对象改为引用对象)
把值对象变为引用对象
重构原因?
值对象一般不应该具有setter方法,如果希望修改数据,并且希望所有引用此对象的地方都能看到这个修改。
Change Reference to Value(把引用对象变成值对象)
把引用对象变成值对象
重构原因?
引用对象不好用了:
- 可能造成内存区域之间错综复杂的关联。
- 在分布系统和并发系统中,不可变的value object特别有用,因为你无须考虑它们的同步问题。
- 我希望简单的访问一些不会被改变的对象
Replace Array with Object(以对象取代数组)
以对象替换数组。对于数组中的每个元素,以一个值域表示之。
1 | String[] row = new String[3]; |
重构原因?
数组应该只用于「以某种顺序容纳一组相似对象」
不要用数组来记录数种不同对象,因为很难记住第一个元素表示什么含义。
Change Unidirectional Association to Bidirectional(将单向关联改为双向)
problem:两个classes都需要使用对方特性,但其间只有一条单向连接(one-way link)。
solution:添加一个反向指针,并使修改函数(modifiers)能够同时更新两条连接。(译注:这里的指针等同于句柄(handle),修改函数(modifier)指的是改变双方关系者)
这个修改函数所在类就是维护方。
重构原因?
随着时间推移,可能发现referred class需要得到其引用者(某个object)以便进行某些处理。
在获取反向引用的过程比较麻烦的时候需要进行重构
如何重构?
- 在class中增加一个值域,用以保存「反向指针」。
- 决定由哪个class (引用端或被引用端)控制关联性(association)。
- 在「被控端」建立一个辅助函数,其命名应该清楚指出它的有限用途。
- 如果既有的修改函数(modifier)在「控制端」,让它负责更新反向指针。
- 如果既有的修改函数(modifier)在「被控端」,就在「控制端」建立一个控制函数,并让既有的修改函数调用这个新建的控制函数。
哪一段比较适合作为控制端?
【一对多】:让多的一方作为控制端
【一对一和多对多】:任意一方
Change Bidirectional Association to Unidirectional(将双向关联改为单向)
problem:两个鄉之间有双向关联,但其中一个class如今不再需要另一个class的特性。
solution:去除不必要的关联(association)。
重构原因?
- 双向关联(bidirectional associations)很有用,但你也必须为它付出代价,那就是「维护双向连接、确保对象被正确创建和删除」而增加的复杂度。
- 大量的双向连接(two-way links)也很容易引发「僵尸对象」:某个对象本来已经该死亡了,却仍然保留在系统中,因为对它的各项引用还没有完全清除。
- 过多的双向关联会导致紧耦合过多,造成一个类的变化对另外的类造成影响。
当不再需要另一个class的特性的时候,可以进行重构。
Replace Magic Number with Symbolic Constant(以符号常量/字面常量取代魔法数)
problem:使用具有特定含义的数字。
solution:将此数字替换为具有解释数字含义的可读名称的常量。
1 | double potentialEnergy(double mass, double height) { |
重构原因?
一旦这些具有特殊含义的数值不断发生改变,将会是一场噩梦。
Encapsulate Field(封装值域)
problem:你的class中存在一个public值域。
solution:将它声明为private,并提供相应的访问函数(accessors)。
和Self Encapsulate Field不同,这里的字段是public的。
1 | public String _name |
重构原因?
符合封装的思想
间接管理更灵活
Encapsulate Collection(封装群集)
有个函数(method)返回一个群集(collection)。
让这个函数返回该群集的一个只读映件(read-only view),并在这个class中提供「添加/移除」(add/remove)群集元素的函数。
1 | class Person{ |
重构原因?
也是封装的思想
不应该让其他类直接获取和修改数据
Replace Type Code with Class(以类取代型别码)
class之中有一个数值型别码(numeric type code),但它并不影响class的行为。
针对JAVA,使用枚举类也行
1 | class Person { |
type code是什么?
表示一系列实体的类型常量值集合
1 | public static final int O = 0; |
不影响class的行为是什么意思?
type code只是纯粹数据
表示class不会因为type code的不同而执行不同的方法,表现不同的行为(比较常见的是type code会在switch语句中引起行为变化时)。
重构原因?
- 以type code为参数的函数实际上,只是接受一个int类型的数据,大大的降低代码的可读性。
- 常量无法进行类型检查,无法避免输入错误带来的影响。
Replace Type Code with Subclasses(以子类取代型别码)
problem:你有一个不可变的(immutable)type code,它会影响class的行为。
solution:以一个subclass 取代这个type code。
1 | //这是一个使用Replace Type Code with Class重构过的类,其实没有必要重构 |
这里只有一处用到switch语句,并且只用于决定创建何种对象,这样的switch语句是可以接受的。
重构原因?
- 每一个方法,都需要根据类型来表现不同的行为,会导致代码中具有很多的switch语句
- 不符合开闭原则,如果需要增加一个type code,就需要在每一个方法的switch中都增加响应的逻辑,这样的工作也是十分头疼的。
Replace Type Code with State/Strategy(以State/strategy 取代型别码)
problem:你有一个type code,它会影响class的行为,但你无法使用subclassing。
solution:以state object(专门用来描述状态的对象)取代type code 。
1 | //这是一个使用Replace Type Code with Class重构过的类,其实没有必要重构 |
Replace Type Code with Subclasses和Replace Type Code with State/Strategy重构后,很自然的会使用Replace Conditional with Polymorphism进行重构,上面都是使用Replace Conditional with Polymorphism重构后的结果
重构原因?
无法使用Replace Type Code with Subclasses:
- 原类已经具有其他子类。
- type code可能在运行的过程中变化(一个不变对象所属类型发生变化),使用子类很难做到这一点。原因是使用Replace Type Code with Subclasses,每一个对象都是一个类型,两者是一体的关系,无法分离开。而Replace Type Code with State/Strategy,对象是对象,类型是类型,分离后就可以实时改变类型。
Replace Subclass with Fields(以值域取代子类)
你的各个subclasses 的惟一差别只在「返回常量数据」的函数身上。
修改这些函数,使它们返回superclass 中的某个(新增)值域,然后销毁subclasses 。
1 |
|
重构原因?
子类没有什么额外的新特性,也没有明显的变化行为。只有一个返回常量的变化行为,这个常量的用途是:不同的子类会返回不同的常量。
除此之外,子类实在没有足够的存在价值