### Replace Type Code with Class(以类取代型别码)
class 之中有一个数值型别码( numeric type code ),但它并不影响class 的行为。
以一个新的class 替换该数值型别码(type code)。
![](https://box.kancloud.cn/2016-08-15_57b1b5a997066.gif)
**动机(Motivation)**
在以C 为基础的编程语言中,type code(型别码)或枚举值(enumerations)很常见。如果带着一个有意义的符号名,type code 的可读性还是不错的。问题在于,符号名终究只是个别名,编译器看见的、进行型别检验的,还是背后那个数值。任何接受type code 作为引数(argument)的函数,所期望的实际上是一个数值,无法强制使用符号名。这会大大降低代码的可读性,从而成为臭虫之源。
如果把那样的数值换成一个class ,编译器就可以对这个class 进行型别检验。只要为这个class 提供factory methods ,你就可以始终保证只有合法的实体才会被创建出 来,而且它们都会被传递给正确的宿主对象。
但是,在使用Replace Type Code with Class 之前,你应该先考虑type code 的其他替换方式。只有当type code 是纯粹数据时(也就是type code 不会在switch 语句中引起行为变化时),你才能以class 来取代它。Java 只能以整数作为switch 语句的「转辙」依据,不能使用任意class ,因此那种情况下不能够以class 替换type code 。更重要的是:任何switch 语句都应该运用 Replace Conditional with Polymorphism 去掉。为了进行那样的重构,你首先必须运用 Replace Type Code with Subclasses 或Replace Type Code with State/Strategy 把type code处理掉。
即使一个type code 不会因其数值的不同而引起行为上的差异,宿主类中的某些行为还是有可能更适合置放于type code class 中,因此你还应该留意是否有必要使用Move Method 将一两个函数搬过去。
**作法(Mechanics)**
- 为type code 建立一个class 。
- 这个class 内需要一个用以记录type code 的值域,其型别应该和type code 相同;并应该有对应的取值函数(getter)。此外还应该用一组static 变量保存「允许被创建」的实体,并以一个对static 函数根据原本的type code 返回合适的实体。
- 修改source class 实现码,让它使用上述新建的class 。
- 维持原先以type code 为基础的函数接口,但改变static 值域,以新建的class 产生代码。然后,修改type code 相关函数,让它们也从新建的class 中获取代码。
- 编译,测试。
- 此时,新建的class 可以对type code 进行运行期检查。
- 对于source class 中每一个使用type code 的函数,相应建立一个函数,让新函数使用新建的class 。
- 你需要建立「以新class 实体为自变量」的函数,用以替换原先「直接以type code 为引数」的函数。你还需要建立一个「返回新class 实体」的函数,用以替换原先「直接返回type code」的函数。建立新函数前,你可以使用Rename Method 修改原函数名称,明确指出那些函数仍然使用旧式的type code ,这往往是个明智之举。
- 逐一修改source class 用户,让它们使用新接口。
- 每修改一个用户,编译并测试。
- 你也可能需要一次性修改多个彼此相关的函数,才能保持这些函数之 间的一致性,才能顺利地编译、测试。
- 删除「使用type code」的旧接口,并删除「保存旧type code」的静态变量。
- 编译,测试。
**范例(Example)**
每个人都拥有四种血型中的一种。我们以Person 来表示「人」,以其中的type code 表示「血型」:
~~~
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) {
_bloodGroup = bloodGroup;
}
public void setBloodGroup(int arg) {
_bloodGroup = arg;
}
public int getBloodGroup() {
return _bloodGroup;
}
}
~~~
首先,我建立一个新的BloodGroup class,用以表示「血型」,并在这个实体中保存原本的type code 数值:
~~~
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);
private static final BloodGroup[] _values = {O, A, B, AB};
private final int _code;
private BloodGroup (int code ) {
_code = code;
}
public int getCode() {
return _code;
}
public static BloodGroup code(int arg) {
return _values[arg];
}
}
~~~
然后,我把Person 中的type code 改为使用BloodGroup class:
~~~
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) {
_bloodGroup = BloodGroup.code(bloodGroup);
}
public int getBloodGroup() {
return _bloodGroup.getCode();
}
public void setBloodGroup(int arg) {
_bloodGroup = BloodGroup.code (arg);
}
}
~~~
现在,我因为BloodGroup class 而拥有了运行期检验能力。为了真正从这些改变中获利,我还必须修改Person 的用户,让它们以BloodGroup 对象表示type code,而不再使用整数。
首先,我使用Rename Method 修改type code 访问函数的名称,说明当前情况:
~~~
class Person...
public int getBloodGroupCode() {
return _bloodGroup.getCode();
}
~~~
然后我为Person 加入一个新的取值函数(getter),其中使用BloodGroup :
~~~
public BloodGroup getBloodGroup() {
return _bloodGroup;
}
~~~
另外,我还要建立新的构造函数和设值函数(setter),让它们也使用BloodGroup :
~~~
public Person (BloodGroup bloodGroup ) {
_bloodGroup = bloodGroup;
}
public void setBloodGroup(BloodGroup arg) {
_bloodGroup = arg;
}
~~~
现在,我要继续处理Person 用户。此时应该注意,每次只处理一个用户,这样才可以保持小步前进。每个用户需要的修改方式可能不同,这使得修改过程更加棘手。 对Person 的static 变量的所有引用点也需要修改。因此,下列代码:
~~~
Person thePerson = new Person(Person.A)
~~~
就变成了:
~~~
Person thePerson = new Person(BloodGroup.A);
~~~
「调用取值函数(getter)」必须改为「调用新取值函数」。因此,下列代码:
~~~
thePerson.getBloodGroupCode()
~~~
变成了 :
~~~
thePerson.getBloodGroup().getCode()
~~~
设值函数(setter)也一样。因此,下列代码:
~~~
thePerson.setBloodGroup(Person.AB)
~~~
变成了 :
~~~
thePerson.setBloodGroup(BloodGroup.AB)
~~~
修改完毕Person 的所有用户之后,我就可以删掉原本使用整数型别的那些旧的取值函数、构造函数、静态变量和设值函数了:
~~~
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();
public Person (int bloodGroup) {
_bloodGroup = BloodGroup.code(bloodGroup);
}
public int getBloodGroup() {
return _bloodGroup.getCode();
}
public void setBloodGroup(int arg) {
_bloodGroup = BloodGroup.code (arg);
}
~~~
我还可以将BloodGroup 中使用整数型别的函数声明为private (因为再没有人会使用它们了):
~~~
class BloodGroup...
private int getCode() {
return _code;
}
private static BloodGroup code(int arg) {
return _values[arg];
}
~~~
- 译序 by 侯捷
- 译序 by 熊节
- 序言
- 前言
- 章节一 重构,第一个案例
- 起点
- 重构的第一步
- 分解并重组statement()
- 运用多态(Polymorphism)取代与价格相关的条件逻辑
- 结语
- 章节二 重构原则
- 何谓重构
- 为何重构
- 「重构」助你找到臭虫(bugs)
- 何时重构
- 怎么对经理说?
- 重构的难题
- 重构与设计
- 重构与性能(Performance)
- 重构起源何处?
- 章节三 代码的坏味道
- Duplicated Code(重复的代码)
- Long Method(过长函数)
- Large Class(过大类)
- Long Parameter List(过长参数列)
- Divergent Change(发散式变化)
- Shotgun Surgery(散弹式修改)
- Feature Envy(依恋情结)
- Data Clumps(数据泥团)
- Primitive Obsession(基本型别偏执)
- Switch Statements(switch惊悚现身)
- Parallel Inheritance Hierarchies(平行继承体系)
- Lazy Class(冗赘类)
- Speculative Generality(夸夸其谈未来性)
- Temporary Field(令人迷惑的暂时值域)
- Message Chains(过度耦合的消息链)
- Middle Man(中间转手人)
- Inappropriate Intimacy(狎昵关系)
- Alternative Classes with Different Interfaces(异曲同工的类)
- Incomplete Library Class(不完美的程序库类)
- Data Class(纯稚的数据类)
- Refused Bequest(被拒绝的遗贈)
- Comments(过多的注释)
- 章节四 构筑测试体系
- 自我测试代码的价值
- JUnit测试框架
- 添加更多测试
- 章节五 重构名录
- 重构的记录格式
- 寻找引用点
- 这些重构准则有多成熟
- 章节六 重新组织你的函数
- Extract Method(提炼函数)
- Inline Method(将函数内联化)
- Inline Temp(将临时变量内联化)
- Replace Temp with Query(以查询取代临时变量)
- Introduce Explaining Variable(引入解释性变量)
- Split Temporary Variable(剖解临时变量)
- Remove Assignments to Parameters(移除对参数的赋值动作)
- Replace Method with Method Object(以函数对象取代函数)
- Substitute Algorithm(替换你的算法)
- 章节七 在对象之间搬移特性
- Move Method(搬移函数)
- Move Field(搬移值域)
- Extract Class(提炼类)
- Inline Class(将类内联化)
- Hide Delegate(隐藏「委托关系」)
- Remove Middle Man(移除中间人)
- Introduce Foreign Method(引入外加函数)
- Introduce Local Extension(引入本地扩展)
- 章节八 重新组织数据
- Self Encapsulate Field(自封装值域)
- Replace Data Value with Object(以对象取代数据值)
- Change Value to Reference(将实值对象改为引用对象)
- Replace Array with Object(以对象取代数组)
- Replace Array with Object(以对象取代数组)
- Duplicate Observed Data(复制「被监视数据」)
- Change Unidirectional Association to Bidirectional(将单向关联改为双向)
- Change Bidirectional Association to Unidirectional(将双向关联改为单向)
- Replace Magic Number with Symbolic Constant(以符号常量/字面常量取代魔法数)
- Encapsulate Field(封装值域)
- Encapsulate Collection(封装群集)
- Replace Record with Data Class(以数据类取代记录)
- Replace Type Code with Class(以类取代型别码)
- Replace Type Code with Subclasses(以子类取代型别码)
- Replace Type Code with State/Strategy(以State/strategy 取代型别码)
- Replace Subclass with Fields(以值域取代子类)
- 章节九 简化条件表达式
- Decompose Conditional(分解条件式)
- Consolidate Conditional Expression(合并条件式)
- Consolidate Duplicate Conditional Fragments(合并重复的条件片段)
- Remove Control Flag(移除控制标记)
- Replace Nested Conditional with Guard Clauses(以卫语句取代嵌套条件式)
- Replace Conditional with Polymorphism(以多态取代条件式)
- Introduce Null Object(引入Null 对象)
- Introduce Assertion(引入断言)
- 章节十一 处理概括关系
- Pull Up Field(值域上移)
- Pull Up Method(函数上移)
- Pull Up Constructor Body(构造函数本体上移)
- Push Down Method(函数下移)
- Push Down Field(值域下移)
- Extract Subclass(提炼子类)
- Extract Superclass(提炼超类)
- Extract Interface(提炼接口)
- Collapse Hierarchy(折叠继承关系)
- Form Template Method(塑造模板函数)
- Replace Inheritance with Delegation(以委托取代继承)
- Replace Delegation with Inheritance(以继承取代委托)
- 章节十二 大型重构
- 这场游戏的本质
- Tease Apart Inheritance(梳理并分解继承体系)
- Convert Procedural Design to Objects(将过程化设计转化为对象设计)
- Separate Domain from Presentation(将领域和表述/显示分离)
- Extract Hierarchy(提炼继承体系)
- 章节十三 重构,复用与现实
- 现实的检验
- 为什么开发者不愿意重构他们的程序?
- 现实的检验(再论)
- 重构的资源和参考资料
- 从重构联想到软件复用和技术传播
- 结语
- 参考文献
- 章节十四 重构工具
- 使用工具进行重构
- 重构工具的技术标准(Technical Criteria )
- 重构工具的实用标准(Practical Criteria )
- 小结
- 章节十五 集成
- 参考书目