×

消息

EU e-Privacy Directive

This website uses cookies to manage authentication, navigation, and other functions. By using our website, you agree that we can place these types of cookies on your device.

View e-Privacy Directive Documents

You have declined cookies. This decision can be reversed.

继承优于标签类型

开发者有时会遇到这样的类:其对象有多种不同"类别",每一"类别"的对象都用一个特定的标签来识别。比如下面的类,可以代表"圆圈"或"矩形":

// 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();
		}
	}
}

这样的设计有很多缺陷:

1. 满是"样板代码"(boilerplate,指那些与核心功能无关,到处重复使用,但又不可或缺的代码),包括枚举声明、标签属性和switch块。
2. 由于多个实现被塞进同一个类中,代码可读性很差。
3. 对于任何一个特定"类别"的对象,其他"类别"独有的属性也都需要被初始化,所以内存开销很大。
4. 属性初始化依赖于构造函数的执行,所以不能声明为final。这会导致更多boilerplate的产生。
5. 构造函数必须正确初始化各"类别"相关的属性,无法获得编译器的辅助。
6. 如果要添加任何新"类别",必须通过修改代码来完成,并且必须确保更新所有的switch语句。
7. 类型本身无法提供任何关于"类别"的信息。

可见这种基于标签的类型定义是繁琐,易出错,而且低效的。很明显,处理上述类型定义需求的最好方式,应该是继承机制。本质上说,上面例子中的标签类型,是对OO语言中子类的拙劣模仿。下面是用子类实现的上例的数据类型:

// 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; }
	
	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;
	}
	
	double area() { return length * width; }
}

这种基于继承的设计可以避免上面提到的标签类型的所有缺陷。而且继承链可以更清楚地反映类型之间的关系,有更好的灵活性和编译阶段的类型验证。比如,假设上面的标签类型同样可以处理"正方形"这个类别,继承链则可以表明"正方形"只是一种特殊的"矩形":

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

为简洁起见,这个例子重用了父类的属性,如果整个继承链是开放为public的,则应避免这种用法(item 14)。

总之,标签类型是一种错误的类型设计,必须被继承架构取代。

提交评论


安全码
刷新