java 中復(fù)合機(jī)制的實(shí)例詳解
java 中復(fù)合機(jī)制的實(shí)例詳解
繼承的缺陷
繼承的缺陷是由它過(guò)于強(qiáng)大的功能所導(dǎo)致的。繼承使得子類(lèi)依賴(lài)于超類(lèi)的實(shí)現(xiàn),從這一點(diǎn)來(lái)說(shuō),就不符合封裝的原則。
一旦超類(lèi)隨著版本的發(fā)布而有所變化,子類(lèi)就有可能遭到破壞,即使它的代碼完全沒(méi)有改變。
為了說(shuō)明的更加具體,假設(shè)我們現(xiàn)在程序中使用到了HashSet,我們需要增加一個(gè)功能,去統(tǒng)計(jì)這個(gè)HashSet自創(chuàng)建以來(lái)一共曾經(jīng)添加過(guò)多少元素。
在還不知道繼承的缺陷的情況下,我們?cè)O(shè)計(jì)了一個(gè)類(lèi),繼承了HashSet,添加了一個(gè)屬性addCount來(lái)進(jìn)行統(tǒng)計(jì),并且復(fù)寫(xiě)了add和addAll方法,在方法里面修改addCount的值,
代碼如下:
public class InstrumentedHashSet<E> extends HashSet<E> { // The number of attempted element insertions 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; } }
這個(gè)類(lèi)看起了合情合理,但是它并不能正常工作,執(zhí)行這段代碼:
public static void main(String[] args) { InstrumentedHashSet<String> s = new InstrumentedHashSet<String>(); s.addAll(Arrays.asList("Snap", "Crackle", "Pop")); System.out.println(s.getAddCount()); // expect 3 but 6 }
因?yàn)橹徊迦肓巳齻€(gè)元素,我們期望getAddCount方法應(yīng)該返回3,然后事實(shí)卻是返回6,哪里出錯(cuò)了?
其實(shí),在HashSet內(nèi)部,addAll方法是基于add方法來(lái)實(shí)現(xiàn)的,因此,使用addAll添加三個(gè)元素,會(huì)調(diào)用一次addAll,三次add。
再看看我們復(fù)寫(xiě)的方法,也就明白為什么getAddCount返回6了。
當(dāng)然,你會(huì)說(shuō),既然HashSet是這樣實(shí)現(xiàn)的,那么我們就不要復(fù)寫(xiě)addAll方法就行了。是的,沒(méi)錯(cuò)。
但是這樣雖然可以正常工作,但是它的正確性卻依賴(lài)于這樣的事實(shí):HashSet的addll方法是在add方法上實(shí)現(xiàn)的。
一旦超類(lèi)修改了實(shí)現(xiàn)細(xì)節(jié),我們的功能就會(huì)有可能受影響。
總的來(lái)說(shuō),繼承存在三個(gè)天然缺陷,這些缺陷會(huì)導(dǎo)致軟件非常脆弱:
1) 子類(lèi)如果調(diào)用了父類(lèi)的方法,那么就會(huì)對(duì)父類(lèi)形成依賴(lài),一旦父類(lèi)做了改動(dòng),子類(lèi)就很可能不能正常工作。
2) 如果父類(lèi)新增了方法,而子類(lèi)恰好已經(jīng)提供了一個(gè)簽名相同但是返回值不同的方法,那么子類(lèi)將無(wú)法通過(guò)編譯。
3) 在不應(yīng)該繼承的時(shí)候使用繼承,會(huì)暴露不必要的API給子類(lèi)。這一點(diǎn),Java平臺(tái)就犯過(guò)錯(cuò),典型的例子就是Properties繼承了HashTable,這是不合理的,屬性列表不是散列表,但是Java代碼里的Properties卻繼承了HashTable,導(dǎo)致用戶(hù)創(chuàng)建Properties實(shí)例后,有put和setProperties兩個(gè)方法,有g(shù)et和getProperties兩個(gè)方法,而put和get方法是不應(yīng)該給用戶(hù)暴露的。
因?yàn)樵赑roperties里,key和value都應(yīng)該是String,而HashMap可以是其他類(lèi)型甚至是對(duì)象。
public class TestProperty { public static void main(String[] args) { Properties properties = new Properties(); properties.setProperty("aaa", "aaa"); properties.put("aaa", new TestPropertyObj()); System.out.println(properties.getProperty("aaa")); // null System.out.println(properties.get("aaa")); // com.hzy.effjava.chp3.item16.TestProperty$TestPropertyObj@5f4fcc96 } static class TestPropertyObj { } }
復(fù)合 繼承的替代方案
上一節(jié)講了繼承的缺陷,這一節(jié)就讓我們來(lái)看看解決這個(gè)問(wèn)題的方案——復(fù)合。
首先我們需要一個(gè)持有Set對(duì)象的一個(gè)類(lèi),這個(gè)類(lèi)實(shí)現(xiàn)了Set接口,實(shí)現(xiàn)方法里調(diào)用了所持有的Set對(duì)象的對(duì)應(yīng)的方法,因此我們也叫它轉(zhuǎn)發(fā)類(lèi):
public class ForwardingSet<E> implements Set<E> { private final Set<E> s; public ForwardingSet(Set<E> s) { this.s = s; } public void clear() { s.clear(); } public boolean contains(Object o) { return s.contains(o); } public boolean isEmpty() { return s.isEmpty(); } public int size() { return s.size(); } public Iterator<E> iterator() { return s.iterator(); } public boolean add(E e) { return s.add(e); } public boolean remove(Object o) { return s.remove(o); } public boolean containsAll(Collection<?> c) { return s.containsAll(c); } public boolean addAll(Collection<? extends E> c) { return s.addAll(c); } public boolean removeAll(Collection<?> c) { return s.removeAll(c); } public boolean retainAll(Collection<?> c) { return s.retainAll(c); } public Object[] toArray() { return s.toArray(); } public <T> T[] toArray(T[] a) { return s.toArray(a); } @Override public boolean equals(Object o) { return s.equals(o); } @Override public int hashCode() { return s.hashCode(); } @Override public String toString() { return s.toString(); } }
接著,我們就可以設(shè)計(jì)具有統(tǒng)計(jì)功能的類(lèi)了,只需要去繼承我們剛剛創(chuàng)建的轉(zhuǎn)發(fā)類(lèi),然后統(tǒng)計(jì)的邏輯代碼的編寫(xiě)即可:
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; } public static void main(String[] args) { InstrumentedSet<String> s = new InstrumentedSet<String>(new HashSet<String>()); s.addAll(Arrays.asList("Snap", "Crackle", "Pop")); System.out.println(s.getAddCount()); } }
這樣的實(shí)現(xiàn)方式,避免了上一節(jié)講的所有問(wèn)題,由于不使用繼承,它不依賴(lài)于超類(lèi)的實(shí)現(xiàn)邏輯,也不用擔(dān)心超類(lèi)新增新的方法對(duì)我們的影響。
而且這樣寫(xiě)還有一個(gè)好處,這個(gè)類(lèi)可以給所有實(shí)現(xiàn)了Set接口的類(lèi)添加統(tǒng)計(jì)功能,而不僅僅是HashSet,還包括TreeSet等其他Set。
其實(shí),這就是裝飾模式,InstrumentedSet對(duì)Set進(jìn)行了修飾,給它增加了計(jì)數(shù)屬性。
總結(jié)
繼承會(huì)破壞類(lèi)的封裝性,導(dǎo)致子類(lèi)非常脆弱,容易受到破壞。
使用復(fù)合的方式,對(duì)超類(lèi)進(jìn)行修飾,使得子類(lèi)更加的健壯,同時(shí)功能更加強(qiáng)大。
如有疑問(wèn)請(qǐng)留言或者到本站社區(qū)交流討論,感謝閱讀,希望能幫助到大家,謝謝大家對(duì)本站的支持!
相關(guān)文章
SpringBoot集成P6Spy實(shí)現(xiàn)SQL日志的記錄詳解
P6Spy是一個(gè)框架,它可以無(wú)縫地?cái)r截和記錄數(shù)據(jù)庫(kù)活動(dòng),而無(wú)需更改現(xiàn)有應(yīng)用程序的代碼。一般我們使用的比較多的是使用p6spy打印我們最后執(zhí)行的sql語(yǔ)句2022-11-11Spring MVC項(xiàng)目中l(wèi)og4J和AOP使用詳解
項(xiàng)目日志記錄是項(xiàng)目開(kāi)發(fā)、運(yùn)營(yíng)必不可少的內(nèi)容,有了它可以對(duì)系統(tǒng)有整體的把控,出現(xiàn)任何問(wèn)題都有蹤跡可尋。下面這篇文章主要給大家介紹了關(guān)于Spring MVC項(xiàng)目中l(wèi)og4J和AOP使用的相關(guān)資料,需要的朋友可以參考下。2017-12-12linux下用renameTo方法修改java web項(xiàng)目中文件夾名稱(chēng)的實(shí)例
下面小編就為大家?guī)?lái)一篇linux下用renameTo方法修改java web項(xiàng)目中文件夾名稱(chēng)的實(shí)例。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-06-06SpringBoot開(kāi)發(fā)存儲(chǔ)服務(wù)器實(shí)現(xiàn)過(guò)程詳解
這篇文章主要為大家介紹了SpringBoot開(kāi)發(fā)存儲(chǔ)服務(wù)器實(shí)現(xiàn)過(guò)程詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-12-12詳解springboot shiro jwt實(shí)現(xiàn)權(quán)限管理
為什么使用jwt呢,因?yàn)榭梢酝ㄟ^(guò)URL,POST參數(shù)或者在HTTP header發(fā)送,因?yàn)閿?shù)據(jù)量小,傳輸速度也很快。本篇通過(guò)具體代碼來(lái)進(jìn)行詳情解析,對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值2021-09-09java數(shù)組實(shí)現(xiàn)隊(duì)列及環(huán)形隊(duì)列實(shí)現(xiàn)過(guò)程解析
這篇文章主要介紹了java數(shù)組實(shí)現(xiàn)隊(duì)列及環(huán)形隊(duì)列實(shí)現(xiàn)過(guò)程解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-10-10Java使用junit框架進(jìn)行代碼測(cè)試過(guò)程詳解
單元測(cè)試就是針對(duì)最小的功能單元編寫(xiě)測(cè)試代碼,Junit是使用Java語(yǔ)言實(shí)現(xiàn)的單元測(cè)試框架,它是開(kāi)源的,Java開(kāi)發(fā)者都應(yīng)當(dāng)學(xué)習(xí)并使用Junit編寫(xiě)單元測(cè)試。本文就來(lái)講講Junit框架的使用教程,需要的可以參考一下2023-02-02