第23项:类层次优于标签类

  有时候,可能会遇到带有两种甚至更多种风格的实例的类,并包含表示实例风格的标签(tag)字段。例如,考虑下面的这个类,它能够表示圆形或者矩形:

// Tagged class - vastly inferior to a class hierarchy!
class Figure {
    enum Shape { RECTANGLE, CIRCLE };
    // Tag field - the shape of this figure
    final Shape shape;
    // These fields are used only if shape is RECTANGLE
    double length;
    double width;
    // This field is used only if shape is CIRCLE
    double radius;
    // Constructor for circle
    Figure(double radius) {
        shape = Shape.CIRCLE;
        this.radius = radius;
    }
    // Constructor for rectangle
    Figure(double length, double width) {
        shape = Shape.RECTANGLE;
        this.length = length;
        this.width = width;
    }
    double area() {
    switch(shape) {
        case RECTANGLE:
            return length * width;
        case CIRCLE:
            return Math.PI * (radius * radius);
        default:
            throw new AssertionError(shape);
        }
    }
}

  这种标记的类有许多缺点。 它们杂乱无章,包括枚举声明,标记字段和switch语句。可读性进一步受到破坏,因为多个实现在一个类中混杂在一起。内存占用量增加,因为实例负担了属于其他风格的不相关字段。 除非构造函数初始化不相关的字段,否则不能将字段设为final,从而产生更多的样板。构造函数必须设置标记字段并在没有编译器帮助的情况下初始化正确的数据字段:如果初始化错误的字段,程序将在运行时失败。除非可以修改其源文件,否则无法向标记类添加类型。如果确实添加了一个类型,则必须记住为每个switch语句添加一个case,否则该类将在运行时失败。最后,实例的数据类型没有给出其类型的线索。简而言之,标记类是冗长的,容易出错且效率低下的。

  幸运的是,面向对象的语言例如Java,就提供了其他更好的方法来定义能表示多种风格对象的单个数据类型:子类型化(subtyping)。标签类正是类层次的一种简单的仿效。

  为了将标签类转变成类层次,首先要为标签类中的每个方法都定义一个包含抽象方法的抽象类,这每个方法的行为都依赖于标签值。在Figure类中,只有一个这样的方法:area。这个抽象类是类层次的根(root)。如果还有其他的方法其行为不依赖于标签的值,就把这样的方法放在这个类中。同样的,如果所有的方法都用到了某些数据字段,就应该把它们放在这个类中。在Figure类中,不存在这种类型独立的方法或者数据字段。

  接下来,为每种原始标签类都定义根类的具体子类。在前面的例子中,这样的类型有两个:原型(circle)和矩形(rectangle)。在每个子类中都包含特定于该类型的数据域。在我们的示例中,radius是圆形特有的,length和width是矩形特有的。同时在每个子类中还包括针对根类中每个抽象方法的相应实现。以下是与原始的Figure类相对应的类层次:

// Class hierarchy replacement for a tagged class
abstract class Figure {
    abstract double area();
}
class Circle extends Figure {
    final double radius;
    Circle(double radius) {
        this.radius = radius;
    }
    @Override double area() {
        return Math.PI * (radius * radius);
    }
}
class Rectangle extends Figure {
    final double length;
    final double width;
    Rectangle(double length, double width) {
        this.length = length;
        this.width = width;
    }
    @Override double area() {
        return length * width;
    }
}

  这个类层次纠正了前面提到过的标签类的所有缺点。这段代码简单且清楚,没有包含在原来的版本中所见到的所有样板代码。每个类型的实现都配有自己的类,这些类都没有受到不相关的数据字段的拖累。所有的字段都是final的。编译器确保每个类的构造器都初始化它的数据字段,对于根类中声明的每个抽象方法,都确保有一个实现。这样就杜绝了由于遗漏switch case而导致运行时失败的可能性。多个程序猿可以独立地扩展层次结构,并且不用访问根类的源代码就能相互操作。每种类型都有一种相关的独立的数据类型,允许程序猿指明变量的类型,限制变量,并将参数输入到特殊的类型。

  类层次结构的另一个优点是,他们可以同来反映类型之间的本质上的层次关系,从而提高灵活性和更好的编译时类型检查。假设原始示例中的标记类也允许使用正方形。可以使类层次结构反映正方形是一种特殊的矩形(假设两者都是不可变的)这一事实:

class Square extends Rectangle {
    Square(double side) {
        super(side, side);
    }
}

  注意,上述层次中的字段是被直接访问的,而不是通过访问方法。这是为了简洁起见才这么做的,如果层次结构是公有的(第16条),则不允许这样做。

  简而言之,标签类很少有适用的时候。当你想要编写一个包含显示标签字段的类时,应该考虑一下,这个标签是否可以被取消,这个类是否可以用类层次来代替。当你遇到一个包含标签字段的现有类时,就要考虑将它重构到一个层次结构中去。

第24项:静态成员类优于非静态成员类(Favor static member classes over nonstatic)

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值