有時候,可能會遇到帶有兩種甚至更多鐘風格的類的實例的類,並包含表示實例風格的(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而導致運行失敗的可能性。多個程序員都可以獨立的擴展層次結構,並且不用訪問根類的資源代碼就能互相操作。每種類型都有一種相關的獨立的數據類型,允許程序員指明變量類型,限制變量,並將參數輸入到特殊的類型。
類層次的另一個好處在於,它們可以用來反應類型之間本質上的層次關系,有助於增強靈活性,並更好的進行編譯時類型檢查。
總而言之,標簽類很少有適用的時候。當你想要編寫一個包含顯示的標簽域的類時,應該考慮一下,這個標簽是否可以被取消,這個類是否可以用類層次來代替,當你遇到一個包含標簽域的現有類時,就要考慮將它重構到一個層次結構中去。