有時候,可能會遇到帶有兩種甚至更多鐘風格的類的實例的類,並包含表示實例風格的(tag)域。例如下面這個類,它能夠表示圓形或者矩形:
/**
* 類層次優先與標簽類
* @author weishiyao
*
*/
// Tagged class - vastly inferior to a class hierarchy
public class Figure1{
enum Shape {
RECTANGLE,
CIRCLE
}
// Tag field - the shape of this figure
final Shape shape;
// These field are use only if shape if RECTANGLE
double length;
double width;
// This field is use only if shape is CIRCLE
double radius;
// Constructor for circle
public Figure1(double radius) {
shape = Shape.CIRCLE;
this.radius = radius;
}
// Constructor for rectangle
public Figure1(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.它們中充斥著樣板代碼,包括枚舉聲明,標簽域以及條件語句。由於許多個實現亂七八糟的擠在了單個類中,破壞了可讀性。
2.內存占用也增加了,因為實例承擔了屬於其他風格的不相關的域。
3.域也不能做成final類型的,除非構造器初始化了不相關的域,產生了更多的樣板代碼。構造器必須不借助編譯器,來設置標簽域,並且初始化正確的數據域;如果初始化了錯誤的域,程序就會在運行的時候出錯。
4.無法給標簽類添加風格,除非可以修改源文件,如果一定要添加風格,就必須給每個條件語句都添加一個條件,否則就會在運行的時候失敗。
5.最後,實例的數據類型沒有提供任何關於其風格的線索
總結:標簽類過於冗長、容易出錯,並且效率低下
幸運的是,面向對象的語言java,提供了其他更好的方法來定義能表示多種風格對象的單個數據類型:子類型化。標簽類正是類層次的一種簡單效仿。
為了將標簽類轉化成類層次,首先要為標簽類中的每一個方法都定義一個包含抽象方法的抽象類,這每個方法的行為都依賴於標簽值。在Figure類中,只有一個這樣的方法:area。這個抽象類是類層次的根。如果還有其他的方法行為不依賴於某個標簽的值,就把這樣的方法放到這個類中。同樣的,如果所有的方法都用到了某些數據域,就應該把他們放在這個類中。在Figure類中,不存在這種類型獨立的方法或者數據域。
/**
* 類層次優於標簽類
* @author weishiyao
*
*/
// Class hierarchy replacement for a tagged class
abstract class Figure2 {
abstract double area();
}
class Circle extends Figure2 {
final double radius;
Circle(double radius) {
this.radius = radius;
}
double area() {
return Math.PI * (radius * radius);
}
}
class Rectangle extends Figure2 {
final double length;
final double width;
Rectangle(double length, double width) {
this.length = length;
this.width = width;
}
double area() {
return length * width;
}
}
這個類糾正了前面提到過的標簽類的所有缺點。這段代碼簡單且清楚,沒有包含在原來版本中所見到的所有樣板代碼。每個類型都有自己的類,這些類都沒有受到不相關的數據域的拖累。所有的域都是final的。編譯器確保每個類的構造器都初始化它的數據域,對於根類中聲明的每個抽象方法,都確保有一個實現。這樣就杜絕了由於遺漏switch case而導致運行失敗的可能性。多個程序員都可以獨立的擴展層次結構,並且不用訪問根類的資源代碼就能互相操作。每種類型都有一種相關的獨立的數據類型,允許程序員指明變量類型,限制變量,並將參數輸入到特殊的類型。
類層次的另一個好處在於,它們可以用來反應類型之間本質上的層次關系,有助於增強靈活性,並更好的進行編譯時類型檢查。
總而言之,標簽類很少有適用的時候。當你想要編寫一個包含顯示的標簽域的類時,應該考慮一下,這個標簽是否可以被取消,這個類是否可以用類層次來代替,當你遇到一個包含標簽域的現有類時,就要考慮將它重構到一個層次結構中去。