文章也上传到
(欢迎关注,欢迎大神提点。)
ITEM 23 优先使用类层级结构,减少使用tag标记类
有时候一个类对象可以表示多种形式,用一个tag值指示这个类对象表示哪一种形式。例如下面表示圆形和矩形的类:
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);
}
}
}
像这样一个tag类有很多的缺点:
- 这是一个杂乱的模版,内部包含枚举、tag变量和switch判断;
- 因为多种实现混杂在一个类中导致可读性很差;
- 类的实例包含与自己特性不相关的属性导致内存占用上升;
- 属性变量不能被设置成final,因为在某种形态下有部分属性不会被构造方法初始化,例如上面的多个构造方法;
- 构造方法中必须构造正确的tag标签,以此区分不同的类形态,如果构造错误将导致类运行时失败;
- 可扩展性方面你只能通过修改源码达到添加一种形态的目的,而且在你添加一个新的tag类型后,还要记得修改switch相应的部分。
总之,tag类是冗长的、易错的、低效率的。
幸运的是,像Java一样的面向对象语言提供了一种更好的可以代表多种形态的替代方案:子类类型。而tag类其实就是模仿子类的一种简单的实现而已。
转换tag类成为继承结构;
- 首先,定义一个抽象类,定义一些依赖于tag值的抽象方法。在Figure类中,只有一个这样的方法就是area。
- 这个抽象类是继承结构的根类,如果还有其他的方法依赖tag的值,那也将他们放到这个类中;
- 同样,把那些被所有形态都会用到的属性也放到这个类中(这里并没有这样的属性);
- 接下来定义子类,在我们的例子中,有两个子类:circle和rectangle。radius属于圆的一部分,lenght和width属于矩形;
- 然后在子类中实现抽象类的方法。
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; }
}
这个类层次修复了刚才提到的所有tag类的缺点。
- 这个代码是简单而且清晰的,没有一个模版代码;
- 每个类仅仅实现自己的逻辑,不会被多余的属性阻碍;
- 所有的属性都是final的,每个构造方法都初始化了自己的属性;
- 每个类都实现了父类的抽象方法;
- 消除了可能由switch错误造成的运行时失败;
- 其他程序员可以轻松的添加其他的子类来扩展功能而不用修改原始类的源码。
类层级带来的其他的优点是,很自然的映射出了类层次的关系,提供了更灵活也更好的编译期检查。这个类层级很容易可以被扩展,例如添加正方形:
class Square extends Rectangle {
Square(double side) {
super(side, side);
}
}
注意,上面例子为了简洁,把数据属性设置成可以直接访问的,而不是通过存取器。真实场景下使用时应该注意public属性的缺陷(Item16)。
总结:tag类总是不太恰当的,如果你打算写一个类用tag来区分不同行为,考虑将其替换成类层级关系。
网友评论