Effective Java 3rd 條目23 類層級(jí)優(yōu)于標(biāo)簽類

偶爾,你可能遇見一個(gè)類译红,它的實(shí)例有兩個(gè)或者更多的特點(diǎn)(flavor),而且包含了一個(gè)標(biāo)簽(tag)域表明這個(gè)實(shí)例的特點(diǎn)兴溜。比如侦厚,考慮如下類,它可以代表一個(gè)圓形或者長方形:

// 標(biāo)簽類 - 大大次于類層級(jí)! 
class Figure { enum Shape { RECTANGLE, CIRCLE };
    // 標(biāo)簽域 - 這個(gè)圖形的形狀 
    final Shape shape;
    
    // 這些域僅僅當(dāng)形狀是RECTANGLE時(shí)使用
    double length; 
    double width;

    // 這個(gè)域僅僅當(dāng)形狀是CIRCLE時(shí)使用
    double radius;

    // 圓形的構(gòu)造子 
    Figure(double radius) {
        shape = Shape.CIRCLE;
        this.radius = radius; 
    }

    // 長方形的構(gòu)造子 
    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); 
        }
    }
} 

這樣的標(biāo)簽類有許多缺點(diǎn)拙徽。它們堆滿了樣板代碼刨沦,包括enum聲明、標(biāo)簽域和switch語句膘怕。因?yàn)槎鄠€(gè)實(shí)現(xiàn)雜亂混合在單個(gè)類中想诅,所以進(jìn)一步損害了可讀性。因?yàn)閷?shí)例承擔(dān)著屬于其他特點(diǎn)的不相關(guān)域岛心,所以內(nèi)存占用增加了来破。域不能夠變成final,除非構(gòu)造子初始化不相關(guān)域忘古,這導(dǎo)致了更多的樣板代碼徘禁。構(gòu)造子必須設(shè)置標(biāo)簽域,而且在沒有編譯器協(xié)助下初始化正確的數(shù)據(jù)域:如果你初始化了錯(cuò)誤域髓堪,那么這個(gè)程序?qū)?huì)在運(yùn)行時(shí)失敗送朱。你不能夠添加新的特點(diǎn)到一個(gè)標(biāo)簽類,除非你改變這個(gè)源文件干旁。如果你確實(shí)要添加一個(gè)特點(diǎn)驶沼,你必須記得添加一個(gè)case到每個(gè)switch語句,否則這個(gè)類將會(huì)在運(yùn)行時(shí)失敗争群。最后商乎,實(shí)例的數(shù)據(jù)類型沒有表明它的特點(diǎn)。簡而言之祭阀,標(biāo)簽類是冗長的鹉戚、容易出錯(cuò)的和低效的

幸運(yùn)的是专控,像Java這樣的面向?qū)ο笳Z言提供了一個(gè)更好的替代方法抹凳,定義可以代表多個(gè)特點(diǎn)的單個(gè)數(shù)據(jù)類型:子類型化。標(biāo)簽類只是類型層級(jí)的蒼白的模仿伦腐。

為了把標(biāo)簽類變成類層級(jí)赢底,首先定義一個(gè)抽象類,它包含了為標(biāo)簽類中每個(gè)方法的抽象方法柏蘑,這個(gè)標(biāo)簽類行為依賴于標(biāo)簽值幸冻。在Figure類中,只有一個(gè)這樣的方法咳焚,即area洽损。這個(gè)抽象類是類層級(jí)的根。如果有任何方法的行為不依賴于標(biāo)簽值革半,把該方法放入到這個(gè)類碑定。相似地,如果所有特點(diǎn)使用的任何數(shù)據(jù)域又官,把它們放到這個(gè)類延刘。Figure類中沒有這樣的獨(dú)立于特點(diǎn)的方法或者域。

其次六敬,為原來的標(biāo)簽類的每個(gè)特點(diǎn)碘赖,定義一個(gè)根類的具體子類。在我們的例子中外构,有兩個(gè):圓形和長方形普泡。在每個(gè)子類中包含特屬于這個(gè)特點(diǎn)的數(shù)據(jù)域。在我們的例子中典勇,radius特屬于圓形劫哼,length和width屬于長方形。而且包含根類中每個(gè)抽象方法的恰當(dāng)實(shí)現(xiàn)割笙。以下是相應(yīng)于原來Figure類的類層級(jí):

// 標(biāo)簽類的類層級(jí)替代 
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; 
    }
}

這個(gè)類層級(jí)更正了前面陳述的標(biāo)簽類的每個(gè)缺點(diǎn)权烧。這個(gè)代碼是簡單和明確的,沒有包含原版里面的樣板代碼伤溉。每個(gè)特點(diǎn)的實(shí)現(xiàn)分配了它自己的類般码,這些類沒有負(fù)擔(dān)不相關(guān)數(shù)據(jù)域。所有的域是final的乱顾。編譯器保證了每個(gè)類的構(gòu)造子初始化了它的數(shù)據(jù)域板祝,而且保證了每個(gè)類為根類中聲明的每個(gè)抽象方法有一個(gè)實(shí)現(xiàn)。這消除了由于缺少switch的case運(yùn)行時(shí)失敗的可能性走净。多個(gè)程序員可以獨(dú)立地和共同地?cái)U(kuò)展這個(gè)層級(jí)券时,而不需要訪問根類的源代碼孤里。每個(gè)特點(diǎn)相關(guān)聯(lián)的單獨(dú)數(shù)據(jù)類型,讓程序員表明這個(gè)變量的特點(diǎn)橘洞,而且限制變量和輸入?yún)?shù)到指定特點(diǎn)捌袜。

類層級(jí)的另外一個(gè)優(yōu)點(diǎn)在于,它們變得反應(yīng)了類型之間的自然層級(jí)關(guān)系炸枣,使得有更好的靈活性和更好的編譯時(shí)類型檢查虏等。假設(shè)原來例子中的標(biāo)簽類也允許正方形。這個(gè)類層級(jí)可以變成反應(yīng)這個(gè)事實(shí):正方形是長方形的特殊類型(假設(shè)兩者都是不可變的):

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

需要注意的是适肠,上面層級(jí)中的域是直接訪問的霍衫,而不是通過訪問器方法。這是為了簡單起見侯养,如果層級(jí)是公開的敦跌,那么應(yīng)該是一個(gè)糟糕的設(shè)計(jì)(條目16)。

總之沸毁,標(biāo)簽類很少是適合的峰髓。如果你傾向于編寫有明顯標(biāo)簽域的類,那么考慮這個(gè)標(biāo)簽是否移除息尺,而且這個(gè)類是否可以用層級(jí)替代携兵。當(dāng)你遇見一個(gè)已經(jīng)存在的帶有標(biāo)簽域的類,考慮把它重構(gòu)為一個(gè)層級(jí)搂誉。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末徐紧,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子炭懊,更是在濱河造成了極大的恐慌并级,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,188評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件侮腹,死亡現(xiàn)場離奇詭異嘲碧,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)父阻,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,464評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門愈涩,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人加矛,你說我怎么就攤上這事履婉。” “怎么了斟览?”我有些...
    開封第一講書人閱讀 165,562評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵毁腿,是天一觀的道長。 經(jīng)常有香客問我,道長已烤,這世上最難降的妖魔是什么鸠窗? 我笑而不...
    開封第一講書人閱讀 58,893評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮胯究,結(jié)果婚禮上塌鸯,老公的妹妹穿的比我還像新娘。我一直安慰自己唐片,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,917評(píng)論 6 392
  • 文/花漫 我一把揭開白布涨颜。 她就那樣靜靜地躺著费韭,像睡著了一般。 火紅的嫁衣襯著肌膚如雪庭瑰。 梳的紋絲不亂的頭發(fā)上星持,一...
    開封第一講書人閱讀 51,708評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音弹灭,去河邊找鬼督暂。 笑死,一個(gè)胖子當(dāng)著我的面吹牛穷吮,可吹牛的內(nèi)容都是我干的逻翁。 我是一名探鬼主播,決...
    沈念sama閱讀 40,430評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼捡鱼,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼八回!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起驾诈,我...
    開封第一講書人閱讀 39,342評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤缠诅,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后乍迄,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體管引,經(jīng)...
    沈念sama閱讀 45,801評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,976評(píng)論 3 337
  • 正文 我和宋清朗相戀三年闯两,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了褥伴。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,115評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡生蚁,死狀恐怖噩翠,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情邦投,我是刑警寧澤伤锚,帶...
    沈念sama閱讀 35,804評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響屯援,放射性物質(zhì)發(fā)生泄漏猛们。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,458評(píng)論 3 331
  • 文/蒙蒙 一狞洋、第九天 我趴在偏房一處隱蔽的房頂上張望弯淘。 院中可真熱鬧,春花似錦吉懊、人聲如沸庐橙。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,008評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽态鳖。三九已至,卻和暖如春恶导,著一層夾襖步出監(jiān)牢的瞬間浆竭,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,135評(píng)論 1 272
  • 我被黑心中介騙來泰國打工惨寿, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留邦泄,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,365評(píng)論 3 373
  • 正文 我出身青樓裂垦,卻偏偏與公主長得像顺囊,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子缸废,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,055評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容