復(fù)合優(yōu)先于繼承

閱讀經(jīng)典——《Effective Java》07

繼承(inheritance)是實(shí)現(xiàn)代碼重用的有力手段私爷,但并非總是最好的選擇。繼承打破了封裝性伞插,因?yàn)樽宇愐蕾囉诔愔刑囟üδ艿膶?shí)現(xiàn)細(xì)節(jié)号阿。超類的實(shí)現(xiàn)有可能隨著發(fā)行版本的不同而有所變化斟珊,導(dǎo)致子類遭到破壞写妥。

  1. 子類遭到破壞的案例
  2. 使用復(fù)合和轉(zhuǎn)發(fā)

子類遭到破壞的案例

假設(shè)有一個程序使用HashSet拳球,為了查看它自創(chuàng)建以來曾經(jīng)添加過多少個元素,我們可以通過繼承擴(kuò)展HashSet珍特,重寫add和addAll方法祝峻。

public class InstrumentedHashSet<E> extends HashSet<E> {
  private int addCount = 0;

  public InstrumentedHashSet() {}

  public InstrumentedHashSet(int initCap, float loadFactor) {
    super(initCap, loadFactor);
  }

  @Override
  public boolean add(E e) {
    addCount ++;
    return super.add(e);
  }

  @Override
  public boolean addAll(Collection<? extends E> c) {
    addCount += c.size();
    return super.addAll(c);
  }

  public int getAddCount() {
    return addCount;
  }
}

這段代碼看上去沒什么問題,假如執(zhí)行下面的程序,我們期望getAddCount返回3莱找,但它實(shí)際上返回的是6酬姆。

InstrumentedHashSet<String> s = new InstrumentedHashSet<String>();
s.addAll(Arrays.asList("Snap", "Crackle", "Pop"));
System.out.println(s.getAddCount());

哪里出錯了?

在HashSet內(nèi)部宋距,addAll方法是基于add方法來實(shí)現(xiàn)的,即使HashSet的文檔中并沒有說明這一細(xì)節(jié)症脂,這也是合理的谚赎。因此InstrumentedHashSet中的addAll方法首先把addCount增加了3,然后利用super.addAll()調(diào)用HashSet的addAll實(shí)現(xiàn)诱篷,在該實(shí)現(xiàn)中又調(diào)用了被InstrumentedHashSet覆蓋了的add方法壶唤,每個元素調(diào)用一次,這三次又分別給addCount增加了1棕所,所以總共增加了6闸盔。

因此,使用繼承擴(kuò)展一個類很危險琳省,父類的具體實(shí)現(xiàn)很容易影響子類的正確性迎吵。而復(fù)合優(yōu)先于繼承告訴我們,不用擴(kuò)展現(xiàn)有的類针贬,而是在新類中增加一個私有域击费,讓它引用現(xiàn)有類的一個實(shí)例。這種設(shè)計稱為復(fù)合(Composition)桦他。

使用復(fù)合和轉(zhuǎn)發(fā)

使用復(fù)合來擴(kuò)展一個類需要實(shí)現(xiàn)兩部分:新的類和可重用的轉(zhuǎn)發(fā)類蔫巩。轉(zhuǎn)發(fā)類用于將所有方法調(diào)用轉(zhuǎn)發(fā)給私有域。這樣得到的類非常穩(wěn)固快压,不依賴于現(xiàn)有類的實(shí)現(xiàn)細(xì)節(jié)圆仔。請看下面的例子。

//Wrapper class - use composition in place of inheritance
public class InstrumentedSet<E> extends ForwardingSet<E>{

    private int addCount = 0;
    
    public InstrumentedSet(Set<E> s) {
        super(s);
    }
    
    @Override
    public boolean add(E e) {
        addCount ++;
        return super.add(e);
    }
    
    @Override
    public boolean addAll(Collection<? extends E> c) {
        addCount += c.size();
        return super.addAll(c);
    }
    
    public int getAddCount() {
        return addCount;
    }
}

//Reusable forwarding class
class ForwardingSet<E> implements Set<E> {
    
    private final Set<E> s;
    
    public ForwardingSet(Set<E> s) {this.s = s;}

    @Override
    public int size() {return s.size();}

    @Override
    public boolean isEmpty() {return s.isEmpty();}

    @Override
    public boolean contains(Object o) {return s.contains(o);}

    @Override
    public Iterator<E> iterator() {return s.iterator();}

    @Override
    public Object[] toArray() {return s.toArray();}

    @Override
    public <T> T[] toArray(T[] a) {return s.toArray(a);}

    @Override
    public boolean add(E e) {return s.add(e);}

    @Override
    public boolean remove(Object o) {return s.remove(o);}

    @Override
    public boolean containsAll(Collection<?> c) {return s.containsAll(c);}

    @Override
    public boolean addAll(Collection<? extends E> c) {return s.addAll(c);}

    @Override
    public boolean retainAll(Collection<?> c) {return s.retainAll(c);}

    @Override
    public boolean removeAll(Collection<?> c) {return s.retainAll(c);}

    @Override
    public void clear() {s.clear();}
    
}

現(xiàn)在蔫劣,使用InstrumentedSet不會再出上面的問題了坪郭,因?yàn)闊o論是add方法還是addAll方法都轉(zhuǎn)發(fā)給了私有域s來處理,這些方法對于s來說總是一致的脉幢,不會受InstrumentedSet的影響截粗。另一個好處是此時的包裝類InstrumentedSet可以用來包裝任何Set實(shí)現(xiàn),有了更廣泛的適用性鸵隧。例如

Set<Date> s = new InstrumentedSet<Date>(new TreeSet<Date>(cmp));
Set<E> s2 = new InstrumentedSet<E>(new HashSet<E>(capacity));

只有當(dāng)子類和超類之間確實(shí)存在父子關(guān)系時绸罗,才可以考慮使用繼承。否則都應(yīng)該用復(fù)合豆瘫,包裝類不僅比子類更加健壯珊蟀,而且功能也更加強(qiáng)大。

關(guān)注作者文集《Effective Java》,第一時間獲取最新發(fā)布文章育灸。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末腻窒,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子磅崭,更是在濱河造成了極大的恐慌儿子,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,284評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件砸喻,死亡現(xiàn)場離奇詭異柔逼,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)割岛,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,115評論 3 395
  • 文/潘曉璐 我一進(jìn)店門愉适,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人癣漆,你說我怎么就攤上這事维咸。” “怎么了惠爽?”我有些...
    開封第一講書人閱讀 164,614評論 0 354
  • 文/不壞的土叔 我叫張陵癌蓖,是天一觀的道長。 經(jīng)常有香客問我婚肆,道長费坊,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,671評論 1 293
  • 正文 為了忘掉前任旬痹,我火速辦了婚禮附井,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘两残。我一直安慰自己永毅,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,699評論 6 392
  • 文/花漫 我一把揭開白布人弓。 她就那樣靜靜地躺著沼死,像睡著了一般。 火紅的嫁衣襯著肌膚如雪崔赌。 梳的紋絲不亂的頭發(fā)上意蛀,一...
    開封第一講書人閱讀 51,562評論 1 305
  • 那天,我揣著相機(jī)與錄音健芭,去河邊找鬼县钥。 笑死,一個胖子當(dāng)著我的面吹牛慈迈,可吹牛的內(nèi)容都是我干的若贮。 我是一名探鬼主播,決...
    沈念sama閱讀 40,309評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼谴麦!你這毒婦竟也來了蠢沿?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,223評論 0 276
  • 序言:老撾萬榮一對情侶失蹤匾效,失蹤者是張志新(化名)和其女友劉穎舷蟀,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體面哼,經(jīng)...
    沈念sama閱讀 45,668評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡野宜,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,859評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了精绎。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片速缨。...
    茶點(diǎn)故事閱讀 39,981評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡锌妻,死狀恐怖代乃,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情仿粹,我是刑警寧澤搁吓,帶...
    沈念sama閱讀 35,705評論 5 347
  • 正文 年R本政府宣布,位于F島的核電站吭历,受9級特大地震影響堕仔,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜晌区,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,310評論 3 330
  • 文/蒙蒙 一摩骨、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧朗若,春花似錦恼五、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,904評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至遣总,卻和暖如春睬罗,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背旭斥。 一陣腳步聲響...
    開封第一講書人閱讀 33,023評論 1 270
  • 我被黑心中介騙來泰國打工容达, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人垂券。 一個月前我還...
    沈念sama閱讀 48,146評論 3 370
  • 正文 我出身青樓董饰,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子卒暂,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,933評論 2 355

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

  • 繼承是實(shí)現(xiàn)代碼重用的有力手段啄栓,但并非總是最好的選擇。因?yàn)閷τ谄胀ǖ木唧w類進(jìn)行跨超包邊界的繼承則是非常危險的也祠,...
    呼天闊閱讀 744評論 0 1
  • (一)Java部分 1昙楚、列舉出JAVA中6個比較常用的包【天威誠信面試題】 【參考答案】 java.lang;ja...
    獨(dú)云閱讀 7,105評論 0 62
  • 對象的創(chuàng)建與銷毀 Item 1: 使用static工廠方法,而不是構(gòu)造函數(shù)創(chuàng)建對象:僅僅是創(chuàng)建對象的方法诈嘿,并非Fa...
    孫小磊閱讀 1,982評論 0 3
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法,類相關(guān)的語法奖亚,內(nèi)部類的語法昔字,繼承相關(guān)的語法作郭,異常的語法夹攒,線程的語...
    子非魚_t_閱讀 31,631評論 18 399
  • 咖啡冥想 恭喜我的雙胞胎3女兒找到了她心意压语,家長們都滿意的男朋友胎食,明天訂婚斥季,已經(jīng)定過婚的二女兒的男朋友今天來家走親...
    靜聞j閱讀 133評論 0 0