Effective Java(3rd)-Item23 类层次优于tagged class

  有时,你可能会遇到一个类,其实例有两个或更多风格,并包含一个标记字段表示了实例的风格。例如,考虑这个类,它能够表示一个圆或一个矩形:


image.png

  这种标记类有许多缺点。它们杂乱无章,包含枚举声明,标签字段和switch语句。可读性进一步受损,因为多个实现在一个类中混在一起。内存占用增加,因为实例负担了属于别的风格的不相干字段。除非构造方法初始化不相干字段,否则不能将字段设为final,从而产生更多样板。构造方法必须设置标记字段并在没有编译器帮助的情况下初始化正确的数据字段:如果你初始化错误的字段,程序将在运行时失败。除非你可以修改其源文件,否则无法向标记类添加别的风格。如果你非要添加新风格,你必须记住在每个switch语句中添加一个case,否则该类将在运行时失败。最后,实例的数据类型没有给出其风格的线索。简而言之,标记类是冗长的,易错的,效率也低下。
  幸运的是,像Java这样的面向对象的语言为顶一个能够表示多种类型对象的单一数据类型提供了更好的选择:子类型。标记类只是对类层次结构的苍白模仿
  要将标记类转为类层次接口,首先要为包含标记类中的每个方法的抽象方法定义一个抽象类,该方法的行为取决于标记值。在Figure类中,只有一个area方法。抽象类是类层次接口的根。如果有任何方法的行为不依赖于标记的值,将它们放在此类中,相似的,如果所有风格都使用了任何数据字段,将它们放在此类中。在Figure类中没有这种与风格无关的方法或字段。
  接下来,为原始标记类的而每个风格定义根类的具体子类。在我们的例子中,有两个:圆和矩形。每个子类包含特定风格的数据字段。在我们的例子中,半径是圆特有的,长和宽特属于矩形。还在每个子类中包含跟类中每个抽象方法的适当实现。如下是原始Figure类对应的类层次结构:

image.png

image.png

  这个类层次纠正了先前提到的标记类的每个缺点。代码简洁明了,不包含原始文件的样板文件。每个风格实现都被分配到它自己的类,并且这些类都没有被无关的数据字段所阻碍。所有域都是final的。编译器确保每个类的构造方法初始化其数据字段,并且每个类都具有针对根类中声明的每个抽象方法的实现。这消除了由于缺少switch case的情况导致的运行时错误的可能性。多个程序员可以独立且可互操作地扩展层次接口,而无需访问根类的源。每个风格都有一个单独的数据类型,允许程序员指出变量的风格,并将输入参数和变量限制为特定的风格。
  类层次结构的另一个优点是它们反映了类型之间的自然层次关系,从而提高灵活性和更好的编译时类型检查。假设在原例中的标记类也允许使用正方形。类层次接口可以反映正方形是一种特殊的矩形(假设两者都是不可变的):


image.png

  注意到,上述层次结构中的字段是可以直接访问的,而不是通过访问方法。这么做是为了简洁,并且如果层级是public的,这将是一个糟糕的设计 (item16).
  总之,标记类很少是适合的需要。如果你尝试编写带有明确的标签字段,考虑标记是否可以删除标记并将类替换为层次接口。当你遇到带有标记字段的现有类时,请你考虑将其重构为层次结构。
本文写于2019.4.16,历时天

你可能感兴趣的:(Effective Java(3rd)-Item23 类层次优于tagged class)