1. 问题及方案
- 表示方式:Replace Value with Object、Change Value to Reference;
- 数组的行为方式很像一种数据结构:Replace Array with Object;
- 魔法数:Replace Magic Number with Symbolic Constant;
- 对象关联方向:Change Unidirectional Association to Bidirectional(单向->双向)、Change Bidirectional Association to Unidirectional(双向->单向);
- 非公开数据:Encapsulate Field、Encapsulate Collection;
- 记录暴露在外:Replace Record with Data Class;
- 类型码(type code):Replace Type Code with Class、Replace Type Code with Subclasses、Replace Type Code with State/Strategy;
2. Self Encapsulate Field(自封装字段)
为这个字段设置取值、设值函数,并只能通过该函数访问。
3. Replace Data Value with Object(以对象取代数据值)
将数据项变成对象。
4. Change Value to Reference(将值对象改为引用对象)
问题:你从一个类衍生出许多彼此相等的实例,希望将它们替换为同一个对象。
将这个值对象改为引用对象。
4.1 动机
- 实例共享,防止多份拷贝;
- 解决方案:如单例模式、静态函数等;
5. Change Reference to Value(将引用对象改为值对象)
问题:你有一个引用对象,很小且不可变,而且不易管理。
将这个引用对象改为值对象。
5.1 动机
- 值对象特性:不可变;
- 提供 equals() 和 hashCode() 等实现;
6. Replace Array with Object(以对象取代数组)
问题:你有一个数组,其中的元素每个都代表不同的东西。ps
:Martin Flower 写《重构》一书时,基于 Java1.0 -> Java1.1 版本,那时还没有泛型一说。现如今应该不会出现这种数组存储完全不同类型的问题,否则就是需求分析建模出错。
以对象取代数组,对于数组中的每个元素,都使用一个字段表示。
6.1 动机
- 数组存储不同类型数据;
- 个人理解另一个问题:数组数据涉及到相关操作,可考虑将属性修改为直接继承已有列表类型的对象,而后扩展相应操作。
7. Duplicate Observed Data(复制“被监视数据”)
将数据复制到一个领域对象中。建立一个 Observer 模式,用以同步领域对象和 GUI 对象内的重复数据。
7.1 动机
- UI 和业务逻辑代码分离;
8. Change Unidirectional Association to Bidirectional(将单向关联改为双向关联)
添加一个反向指针,并使修改函数能够同时更新两条连接。
8.1 动机
- 被引用类需要其引用者以便进行某些处理。
9. Change Bidirectional Association to Unidirectional(将双向关联改为单向关联)
去除不必要的关联。
9.1 动机
- 双向关联维护复杂,引用垃圾回收不了;
- 依赖关系太强,导致紧耦合;
10. Replace Magic Number with Symbolic Constant(使用字面常量替换魔法数)
创造一个变量,根据其意义为它命名,并将上述的字面数值替换为这个常量。ps
:
- 常量不会造成任何性能开销,却可以大大提高代码可读性。
- 若魔法数是类型码,考虑使用 Replace Type Code with Class;
11. Encapsulate Field(封装字段)
将字段声明为 private,并提供 get/set 方法。ps
:面向对象的原则之一就是封装,或称为“数据隐藏”。故数据不应该声明为 public,而通过(函数)行为暴露。
12. Encapsulate Collection(封装集合)
让这个函数返回该集合的一个只读副本,并在这个类中提供添加/删除集合元素的函数。
12.1 动机
- 集合用来保存一组实例,并提供取值/设值函数;
- 取值函数不应该返回集合本身,因为这会使得用户修改集合内容而集合拥有者却不知晓,过多暴露对象内部数据结构的信息;
- 不应该为集合提供一个设置函数,而应该提供添加/删除元素的函数;
- 封装数组,直接将数组替换为其他集合;
13. Replace Record with Data Class(以数据类取代记录)
为该记录创建一个“哑”数据对象。ps
:其实就是单独建立一个实体(Model/Entity),只有取值/设值函数。
14. Replace Type Code with Class(以类取代状态码)
以一个新的类替换该数值类型码。ps
:
- 现如今有枚举类型,强类型检查,可优先考虑。
- 或者单独建立一个类,提供相应的状态码的静态字段,类似于 C# 中 Encoding 类的实现。
15. Replace Type Code with Subclasses(以子类取代类型码)
以子类取代这个类型码。ps
:
- 主要作用是为 Replace Conditional with Polymorphism 搭建平台。
- 若宿主类中无条件表达式,推荐使用 Replace Type Code with Class。
15.1 动机
- 以类型码的宿主类作为基类,针对每种类型码建立相应子类;
- 把“对不同行为的了解”从类用户转移到类自身;
- 不适用情况,需使用 Replace Type Code with State/Strategy:
- 类型码在对象创建后发生改变;
- 由于某种原因,类型码宿主已有子类;
16. Replace Type Code with State/Strategy(使用 State/Strategy 模式取代类型码)
以状态对象取代类型码。
- 状态:State;
- 算法:Strategy;
17. Replace Subclass with Fields(以字段取代子类)
问题:各个子类的唯一差别在于“返回常量数据”的函数上。
修改这些函数,使它们返回超类中的某个(新增)字段,然后销毁子类。
17.1 动机
建立子类的目的是为了增加新特性或变化行为。其中,有一类行为变化被称为“常量函数”,它们会返回一个硬编码的值,使得不同子类的同一个函数返回不同的值。
若子类只有常量函数,则没存在的价值。可直接在超类中使用字段表示类型(如 C# 中的 Encoding 类)
参考
- 《重构-改善既有代码的设计》(第8章)