首页/开发/代码质量与评审速查/手法:Replace Type Code with Class / Subclasses
重构手法62 条

手法:Replace Type Code with Class / Subclasses

用枚举、值类或子类取代魔法数字与字符串编码:取值集合受类型系统约束,比较与分支更安全;行为差异明显时再用子类分解编码行为。

Replace Type Code 是什么

用枚举、值类或子类取代魔法数字与字符串编码:取值集合受类型系统约束,比较与分支更安全,非法值在编译期就被拦下。演进路径通常是常量 → 枚举或值类 → 子类,只有当不同编码对应明显不同的行为时,才值得用子类把行为分解开。

基本信息

演进常量 → 枚举/值类 → 子类(行为差异大时)
收益非法取值在编译期被拦下,分支可被穷尽检查
持久化显式指定存储值,不依赖枚举顺序
适用状态码、类型标识、单位、等级、血型这类有限集合

判断与做法

  • 集合有限且稳定:优先枚举,switch 可获得穷尽性检查
  • 需要携带额外数据(显示名、费率):用带字段的枚举或值类
  • 编码与行为一一对应且行为复杂:考虑子类或策略加枚举
  • 对外协议里的编码值:在边界处转换,内部用类型表达
  • 已有 int 常量的地方先加转换方法,再逐步替换调用点
  • 数据库列保持原编码,映射层负责与枚举互转

Java 示例

Java 示例
// 之前:用 int 编码血型,任何整数都能传进来
static final int O = 0;
static final int A = 1;
static final int B = 2;
static final int AB = 3;

// 之后:值域受类型约束,并可携带额外信息
enum BloodGroup {
    O("O", 0), A("A", 1), B("B", 2), AB("AB", 3);

    private final String label;
    private final int storedValue;   // 显式存储值,不依赖 ordinal

    BloodGroup(String label, int storedValue) {
        this.label = label;
        this.storedValue = storedValue;
    }
    static BloodGroup fromStored(int v) {
        for (BloodGroup g : values()) if (g.storedValue == v) return g;
        throw new IllegalArgumentException("unknown blood group: " + v);
    }
}

演进阶段选择

阶段形态适用信号
一常量集合仅用于比较,数量少
二枚举 / 值类需要穷尽分支、携带附加数据
三子类 / 策略每个取值对应全不同的行为
边界字符串编码对外协议约定,转成内部类型后不再外泄
枚举的持久化值不要依赖 ordinal:插入一个新枚举项就会让后续所有值的含义错位,历史数据读出来就变了。显式指定存储值,并在映射方法里做严格校验。
类型码集合频繁扩展时,子类方案会让类数量随组合增长,此时用「枚举加策略」更合适:枚举承担类型标识,策略承担行为,扩展时新增策略实现而不动调用方。

易错点

  • 枚举持久化值依赖 ordinal:插入新项即错位,历史数据被错误解读
  • 编码集合频繁扩展仍用子类:类数量随组合增长,分支反而更多
  • 只在内部改成枚举而外部协议不变:边界处来回转换散落多处,容易出现不一致
  • 用字符串编码直接比较:拼写错误只能在运行时暴露,且难以穷尽分支

数据来源与参考

  • 《重构》第 2 版 · 第 12 章(Replace Type Code with Class / Subclasses)
​‌‌​​‌​​​‌‌​‌‌‌‌​‌‌​​​‌‌​‌‌‌​​‌‌​​‌​‌‌‌​​‌‌‌​​​‌​‌‌​‌​​‌​‌‌‌​​​‌​‌‌​‌​​‌​‌‌​​​‌‌​‌‌‌​‌​​​‌‌​‌‌‌‌​​‌​‌‌‌​​‌‌​​​‌‌​‌‌​‌‌‌‌​‌‌​‌‌​‌