现在的位置: 首页 > 综合 > 正文

重新组织数据之十三 :Replace Type Code with Class(以类取代型别码)

2014年02月24日 ⁄ 综合 ⁄ 共 4436字 ⁄ 字号 评论关闭

class 之中有一个数值型别码( numeric type code ),但它并不影响class 的行为。

以一个新的class 替换该数值型别码(type code)。

动机(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];

   }

【上篇】
【下篇】

抱歉!评论已关闭.