Effective Java 第三版——23. 優先使用類層次而不是標籤類

Tips
《Effective Java, Third Edition》一書英文版已經出版,這本書的第二版想必不少人都讀過,號稱Java四大名著之一,不過第二版2009年出版,到如今已經將近8年的時間,但隨着Java 6,7,8,甚至9的發佈,Java語言發生了深入的變化。
在這裏第一時間翻譯成中文版。供你們學習分享之用。程序員

Effective Java, Third Edition

23. 優先使用類層次而不是標籤類

有時你可能會碰到一個類,它的實例有兩個或更多的風格,而且包含一個標籤屬性(tag field),表示實例的風格。 例如,考慮這個類,它能夠表示一個圓形或矩形:ide

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

這樣的標籤類具備許多缺點。 他們雜亂無章的樣板代碼,包括枚舉聲明,標籤屬性和switch語句。 可讀性更差,由於多個實如今一個類中混雜在一塊兒。 內存使用增長,由於實例負擔屬於其餘風格不相關的領域。 屬性不能成爲final,除非構造方法初始化不相關的屬性,致使更多的樣板代碼。 構造方法在編譯器的幫助下,必須設置標籤屬性並初始化正確的數據屬性:若是初始化錯誤的屬性,程序將在運行時失敗。 除非能夠修改其源文件,不然不能將其添加到標記的類中。 若是你添加一個風格,你必須記得給每一個switch語句添加一個case,不然這個類將在運行時失敗。 最後,一個實例的數據類型沒有提供任何關於風格的線索。 總之,標籤類是冗長的,容易出錯的,並且效率低下學習

幸運的是,像Java這樣的面向對象的語言爲定義一個可以表示多種風格對象的單一數據類型提供了更好的選擇:子類型化(subtyping)。標籤類僅僅是一個類層次的簡單的模仿。this

要將標籤類轉換爲類層次,首先定義一個包含抽象方法的抽象類,該標籤類的行爲取決於標籤值。 在Figure類中,只有一個這樣的方法,就是area方法。 這個抽象類是類層次的根。 若是有任何方法的行爲不依賴於標籤的值,把它們放在這個類中。 一樣,若是有全部的方法使用的數據屬性,把它們放在這個類。Figure類中不存在這種與類型無關的方法或屬性。翻譯

接下來,爲原始標籤類的每種類型定義一個根類的具體子類。 在咱們的例子中,有兩個類型:圓形和矩形。 在每一個子類中包含特定於改類型的數據字段。 在咱們的例子中,半徑屬性是屬於圓的,長度和寬度屬性都是矩形的。 還要在每一個子類中包含根類中每一個抽象方法的適當實現。 這裏是對應於Figure類的類層次:設計

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

    @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; }
}

這個類層次糾正了以前提到的標籤類的每一個缺點。 代碼簡單明瞭,不包含原文中的樣板文件。 每種類型的實現都是由本身的類來分配的,而這些類都沒有被無關的數據屬性所佔用。 全部的屬性是final的。 編譯器確保每一個類的構造方法初始化其數據屬性,而且每一個類都有一個針對在根類中聲明的每一個抽象方法的實現。 這消除了因爲缺乏switch-case語句而致使的運行時失敗的可能性。 多個程序員能夠獨立地繼承類層次,而且能夠相互操做,而無需訪問根類的源代碼。 每種類型都有一個獨立的數據類型與之相關聯,容許程序員指出變量的類型,並將變量和輸入參數限制爲特定的類型。code

類層次的另外一個優勢是可使它們反映類型之間的天然層次關係,從而提升了靈活性,並提升了編譯時類型檢查的效率。 假設原始示例中的標籤類也容許使用正方形。 類層次能夠用來反映一個正方形是一種特殊的矩形(假設它們是不可變的):對象

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

請注意,上述層中的屬性是直接訪問的,而不是訪問方法。 這裏是爲了簡潔起見,若是類層次是公開的(條目16),這將是一個糟糕的設計。blog

總之,標籤類不多有適用的狀況。 若是你想寫一個帶有明顯標籤屬性的類,請考慮標籤屬性是否能夠被刪除,而類是否被類層次替換。 當遇到一個帶有標籤屬性的現有類時,能夠考慮將其重構爲一個類層次中。繼承

相關文章
相關標籤/搜索