作者的原標題是<PRefer class hierarchies to tagged classes>,即用類層次優于tagged class。
我不知道有沒有tagged class這么一說,其實作者指的tagged class的是一個類描述了多種抽象,可以根據某個field決定不同的實例。下面是書中例子,使用shape和部分表示長度的field構成形狀并計算面積,腦補一下什么是tagged class:
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(); } }}不難看出這個類想傳達什么信息,也不難看出這樣的方式有很多缺陷。雖然能看懂是什么意思,但由于各種實現擠在一個類中,其可讀性并不好。不同的實現放到一個類里描述,即會根據實現的不同而使用不同的field,即,field無法聲明為final。(難道要在構造器里處理不用的field?)雖然微不足道,內存確實存在毫無意義的占用。不夠OO。
雖然上一篇把類層次說得一無是處,其實類層次就是用來解決這一問題的,而上面的tagged class是用非OO的方式模仿類層次。將tagged class轉為類層次,首先要將tagged class里的行為抽象出來,并為其提供抽象類。以上面的Figure為例,我們之需要一個方法——area。接下來需要為每一個tag定義具體子類,即例子中的circle和rectangle。然后為子類提供相應的field,即circle中的radius和rectangle的width、length。最后為子類提供抽象方法的相應實現。其實都不用這樣去說明轉換步驟,因為OO本身就是很自然的東西。
轉換結果如下:
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); }}這樣做的好處顯而易見,代碼簡單清晰,沒有樣板代碼;類型相互獨立,不會受到無關field的影響,field可以聲明為final。子類行可以獨立進行擴展,互不干擾。
回到最初的tagged class,它真的就一無是處?如果使用這個類,我只需要在調用構造器時使用相應的參數就可以得到想要的實例。就像策略模式那樣。當然,tagged class也許可能算是策略模式(傳遞的應該是某個行為的特征,而不是實例特征),但策略模式在Java中并不是這樣使用。
通常,一個策略是通過調用者通過傳遞函數來指定特定的行為的。但Java是沒有函數指針的,所以我們用對象引用實現策略模式,即調用該對象的方法。對于這種僅僅作為一個方法的"載體",即實例等同與方法指針的對象,作者將其稱為函數對象(function object)。
舉個例子,比如我們有這樣的一個具體策略(concrete strategy):
class StringLengthComparator { private StringLengthComparator() { } public static final StringLengthComparator INSTANCE = new StringLengthComparator(); public int compare(String s1, String s2) { return s1.length() - s2.length(); }}具體策略的引用可以說是一個函數指針。對具體策略再抽象一層即成為一個策略接口(strategy interface)。對于上面的例子,java.util中正好有Comparator:
public interface Comparable<T> { int compare(T o1, T o2); boolean equals(Object obj);}于是,我們使用的時候可能會用匿名類傳遞一個具體策略:
Arrays.sort(stringArray, new Comparator<String>() { public int compare(String s1, String s2) { return s1.length() - s2.length(); }});用代碼描述似乎是那么回事,我只是在我想使用的地方傳遞了一個具體策略。缺點很明顯——每次調用的時候又需要創建一個實例。但又能怎么做? 把每個可能用到的具體策略在某個Host類里實現策略接口后都做成field并用final聲明?感覺很傻,但確實可以考慮,因為這樣做還有其他好處。比如,相比匿名類,具體策略可以有更直觀的命名,而且具體策略可以實現其他接口。
代碼如下:
// Exporting a concrete strategyclass Host { private static class StrLenCmp implements Comparator<String>, Serializable { public int compare(String s1, String s2) { return s1.length() - s2.length(); } } // Returned comparator is serializable public static final Comparator<String> STRING_LENGTH_COMPARATOR = new StrLenCmp(); ... // Bulk of class omitted}新聞熱點
疑難解答