亚洲免费在线-亚洲免费在线播放-亚洲免费在线观看-亚洲免费在线观看视频-亚洲免费在线看-亚洲免费在线视频

Effective Java (類和接口)

系統(tǒng) 1731 0

十三、使類和成員的可訪問性最小化:

?? ?? 信息隱藏是軟件程序設(shè)計(jì)的基本原則之一,面向?qū)ο笥譃檫@一設(shè)計(jì)原則提供了有力的支持和保障。這里我們簡要列出幾項(xiàng)受益于該原則的優(yōu)勢:
?? ?? 1.?? ?更好的解除各個(gè)模塊之間的耦合關(guān)系:
? ? ? 由于模塊間的相互調(diào)用是基于接口契約的,每個(gè)模塊只是負(fù)責(zé)完成自己內(nèi)部既定的功能目標(biāo)和單元測試,一旦今后出現(xiàn)性能優(yōu)化或需求變更時(shí),我們首先需要做的便是定位需要變動(dòng)的單個(gè)模塊或一組模塊,然后再針對(duì)各個(gè)模塊提出各自的解決方案,分別予以改動(dòng)和內(nèi)部測試。這樣便大大降低了因代碼無規(guī)則交叉而帶來的潛在風(fēng)險(xiǎn),同時(shí)也縮減了開發(fā)周期。
? ? ? 2.?? ?最大化并行開發(fā):
? ? ? 由于各個(gè)模塊之間保持著較好的獨(dú)立性,因此可以分配更多的開發(fā)人員同時(shí)實(shí)現(xiàn)更多的模塊,由于每個(gè)人都是將精力完全集中在自己負(fù)責(zé)和擅長的專一領(lǐng)域,這樣不僅提高了軟件的質(zhì)量,也大大加快了開發(fā)的進(jìn)度。
? ? ? 3.?? ?性能優(yōu)化和后期維護(hù):
? ? ? 一般來說,局部優(yōu)化的難度和可行性總是要好于來自整體的優(yōu)化,事雖如此,然而我們首先需要做的卻是如何定位需要優(yōu)化的局部,在設(shè)計(jì)良好的系統(tǒng)中,完成這樣的工作并非難事,我們只需針對(duì)每個(gè)涉及的模塊做性能和壓力測試,之后再針對(duì)測試的結(jié)果進(jìn)行分析并拿到相對(duì)合理的解決方案。
? ? ? 4.?? ?代碼的高可復(fù)用性:
? ? ? 在軟件開發(fā)的世界中,提出了眾多的設(shè)計(jì)理論,設(shè)計(jì)原則和設(shè)計(jì)模式,之所以這樣,一個(gè)非常現(xiàn)實(shí)的目標(biāo)之一就是消除重復(fù)代碼,記得《重構(gòu)》中有這樣的一句話:“重復(fù)代碼,萬惡之源”。可見提高可用代碼的復(fù)用性不僅對(duì)編程效率和產(chǎn)品質(zhì)量有著非常重要的意義,對(duì)日后產(chǎn)品的升級(jí)和維護(hù)也是至關(guān)重要的。說一句比較現(xiàn)實(shí)的話,一個(gè)設(shè)計(jì)良好的產(chǎn)品,即使因?yàn)槟承┰驅(qū)е率。敲串a(chǎn)品中應(yīng)用到的一個(gè)個(gè)獨(dú)立、可用和高效的模塊也為今后的東山再起提供了一個(gè)很好的技術(shù)基礎(chǔ)。
? ? ? 讓我們重新回到主題,Java通過訪問控制的方式來完成信息隱藏,而我們的原則是盡可能的使每個(gè)類的域成員不被外界訪問。對(duì)于包內(nèi)的類而言,則盡可能少的定義公有類,遵循這樣的原則可以極大的降低因包內(nèi)設(shè)計(jì)或?qū)崿F(xiàn)的改變而給該包的使用者帶來的影響。當(dāng)然達(dá)到這個(gè)目標(biāo)的一個(gè)重要前提是定義的接口足以完成調(diào)用者的需求。
? ? ? 該條目給出了一個(gè)比較重要的建議,既不要提供直接訪問或通過函數(shù)返回可變域?qū)ο蟮膶?shí)例,見下例:
? ? ?? public final Thing[] values = { ... };
? ? ? 即便Thing數(shù)組對(duì)象本身是final的,不能再被賦值給其他對(duì)象,然而數(shù)組內(nèi)的元素是可以改變的,這樣便給外部提供了一個(gè)機(jī)會(huì)來修改內(nèi)部數(shù)據(jù)的狀態(tài),從而在主類未知的情況下破壞了對(duì)象內(nèi)部的狀態(tài)或數(shù)據(jù)的一致性。其修訂方式如下:

        
          1
        
        
          private
        
        
          static
        
        
          final
        
         Thing[] PRIVATE_VALUES = { ... };

        
          2
        
        
          public
        
        
          static
        
        
          final
        
         Thing[] values() {

        
          3
        
        
          return
        
         PRIVATE_VALUES.clone();

        
          4
        
             }    
      

? ? ? 總而言之,你應(yīng)該盡可能地降低可訪問性。你在仔細(xì)地設(shè)計(jì)了一個(gè)最小的公有API之后,應(yīng)該防止把任何散亂的類、接口和成員變成API的一部分。除了公有靜態(tài)final域的特殊情形之外,公有類都不應(yīng)該包含公有域。并且要確保公有靜態(tài)final域所引用的對(duì)象都是不可變的。

十四、在公有類中使用訪問方法而非公有域:

? ? ? 這個(gè)條目簡短的標(biāo)題已經(jīng)非常清晰的表達(dá)了他的含義,我們這里將只是列出幾點(diǎn)說明:
? ? ? 1.?? ?對(duì)于公有類而言,由于存在大量的使用者,因此修改API接口將會(huì)給使用者帶來極大的不便,他們的代碼也需要隨之改變。如果公有類直接暴露了域字段,一旦今后需要針對(duì)該域字段添加必要的約束邏輯時(shí),唯一的方法就是為該字段添加訪問器接口,而已有的使用者也將不得不更新其代碼,以避免破壞該類的內(nèi)部邏輯。
? ? ? 2.?? ?對(duì)于包級(jí)類和嵌套類,公有的域方法由于只能在包內(nèi)可以被訪問,因而修改接口不會(huì)給包的使用者帶來任何影響。
? ? ? 3.?? ?對(duì)于公有類中的final域字段,提供直接訪問方法也會(huì)帶來負(fù)面的影響,只是和非final對(duì)象相比可能會(huì)稍微好些,如final的數(shù)組對(duì)象,即便數(shù)組對(duì)象本身不能被修改,但是他所包含的數(shù)組成員還是可以被外部改動(dòng)的,針對(duì)該情況建議提供API接口,在該接口中可以添加必要的驗(yàn)證邏輯,以避免非法數(shù)據(jù)的插入,如:

        
          1
        
        
          public
        
         <T> 
        
          boolean
        
         setXxx(
        
          int
        
         index, T value) {

        
          2
        
        
          if
        
         (index > myArray.length) 

        
          3
        
        
          return
        
        
          false
        
        ;

        
          4
        
        
          if
        
         (!(value 
        
          instanceof
        
         LegalClass))

        
          5
        
        
          return
        
        
          false
        
        ;

        
          6
        
                 ...

        
          7
        
        
          return
        
        
          true
        
        ;

        
          8
        
             }
      

十五、使可變性最小化:

?? ?? 只在類構(gòu)造的時(shí)候做初始化,構(gòu)造之后類的外部沒有任何方法可以修改類成員的狀態(tài),該對(duì)象在整個(gè)生命周期內(nèi)都會(huì)保持固定不變的狀態(tài),如String、Integer等。不可變類比可變類更加易于設(shè)計(jì)、實(shí)現(xiàn)和使用,而且線程安全。
? ? ? 使類成為不可變類應(yīng)遵循以下五條原則:
? ? ? 1.?? ?不要提供任何會(huì)修改對(duì)象狀態(tài)的方法;
? ? ? 2.?? ?保證類不會(huì)被擴(kuò)展,既聲明為final類,或?qū)?gòu)造函數(shù)定義為私有;
?? ?? 3.?? ?使所有的域都是final的;
? ? ? 4.?? ?使所有的域都成為私有的;
? ? ? 5.?? ?確保在返回任何可變域時(shí),返回該域的deep copy。
? ? ? 見如下Complex類:

        
           1
        
        
          final
        
        
          class
        
         Complex {

        
           2
        
        
          private
        
        
          final
        
        
          double
        
         re;

        
           3
        
        
          private
        
        
          final
        
        
          double
        
         im;

        
           4
        
        
          public
        
         Complex(
        
          double
        
         re,
        
          double
        
         im) {

        
           5
        
        
          this
        
        .re = re;

        
           6
        
        
          this
        
        .im = im;

        
           7
        
                 }

        
           8
        
        
          public
        
        
          double
        
         realPart() {

        
           9
        
        
          return
        
         re;

        
          10
        
                 }

        
          11
        
        
          public
        
        
          double
        
         imaginaryPart() {

        
          12
        
        
          return
        
         im;

        
          13
        
                 }

        
          14
        
        
          public
        
         Complex add(Complex c) {

        
          15
        
        
          return
        
        
          new
        
         Complex(re + c.re,im + c.im);

        
          16
        
                 }

        
          17
        
        
          public
        
         Complex substract(Complex c) {

        
          18
        
        
          return
        
        
          new
        
         Complex(re - c.re, im - c.im);

        
          19
        
                 }

        
          20
        
                 ... ...

        
          21
        
             }
      

? ? ? 不可變對(duì)象還有一個(gè)對(duì)象重用的優(yōu)勢,這樣可以避免創(chuàng)建多余的新對(duì)象,這樣也能減輕垃圾收集器的壓力,如:
? ? ?? public static final Complex ZERO = new Complex(0,0);
? ? ? public static final Complex ONE = new Complex(1,0);
? ? ? 這樣使用者可以重復(fù)使用上面定義的兩個(gè)靜態(tài)final類,而不需要在每次使用時(shí)都創(chuàng)建新的對(duì)象。
? ? ? 從Complex.add和Complex.substract兩個(gè)方法可以看出,每次調(diào)用他們的時(shí)候都會(huì)有新的對(duì)象被創(chuàng)建,這樣勢必會(huì)帶來一定的性能影響,特別是對(duì)于copy開銷比較大的對(duì)象,如包含幾萬Bits的BigInteger。如果我們所作的操作僅僅是修改其中的某個(gè)Bit,如bigInteger.flipBit(0),該操作只是修改了第0位的狀態(tài),而BigInteger卻為此copy了整個(gè)對(duì)象并返回。鑒于此,該條目推薦為不可變對(duì)象提供一個(gè)功能相仿的可變類,如java.util.BitSet之于java.math.BigInteger。如果我們在實(shí)際開發(fā)中確實(shí)遇到剛剛提及的場景,那么使用BitSet或許是更好的選擇。
? ? ? 對(duì)于不可變對(duì)象還有比較重要的優(yōu)化技巧,既某些關(guān)鍵值的計(jì)算,如hashCode,可以在對(duì)象構(gòu)造時(shí)或留待某特定方法(Lazy Initialization)第一次調(diào)用時(shí)進(jìn)行計(jì)算并緩存到私有域字段中,之后再獲取該值時(shí),可以直接從該域字段獲取,避免每次都重新計(jì)算。這樣的優(yōu)化主要是依賴于不可變對(duì)象的域字段在構(gòu)造后即保持不變的特征。
?? ?
十六、復(fù)合優(yōu)先于繼承:

?? ?? 由于繼承需要透露一部分實(shí)現(xiàn)細(xì)節(jié),因此不僅需要超類本身提供良好的繼承機(jī)制,同時(shí)也需要提供更好的說明文檔,以便子類在覆蓋超類方法時(shí),不會(huì)引起未知破壞行為的發(fā)生。需要特別指出的是對(duì)于跨越包邊界的繼承,很可能超類和子類的實(shí)現(xiàn)者并非同一開發(fā)人員或同一開發(fā)團(tuán)隊(duì),因此對(duì)于某些依賴實(shí)現(xiàn)細(xì)節(jié)的覆蓋方法極有可能會(huì)導(dǎo)致預(yù)料之外的結(jié)果,還需要指出的是,這些細(xì)節(jié)對(duì)于超類的普通用戶來說往往是不看見的,因此在未來的升級(jí)中,該實(shí)現(xiàn)細(xì)節(jié)仍然存在變化的可能,這樣對(duì)于子類的實(shí)現(xiàn)者而言,在該細(xì)節(jié)變化時(shí),子類的相關(guān)實(shí)現(xiàn)也需要做出必要的調(diào)整,見如下代碼:

        
           1
        
        
          //
        
        
          這里我們需要擴(kuò)展HashSet類,提供新的功能用于統(tǒng)計(jì)當(dāng)前集合中元素的數(shù)量,

        
        
           2
        
        
          //
        
        
          實(shí)現(xiàn)方法是新增一個(gè)私有域變量用于保存元素?cái)?shù)量,并每次添加新元素的方法中

        
        
           3
        
        
          //
        
        
          更新該值,再提供一個(gè)公有的方法返回該值。
        
        
        
        
           4
        
        
          public
        
        
          class
        
         InstrumentedHashSet<E> 
        
          extends
        
         HashSet<E> {

        
           5
        
        
          private
        
        
          int
        
         addCount = 0;

        
           6
        
        
          public
        
         InstrumentedHashSet() {}

        
           7
        
        
          public
        
         InstrumentedHashSet(
        
          int
        
         initCap,
        
          float
        
         loadFactor) {

        
           8
        
        
          super
        
        (initCap,loadFactor);

        
           9
        
                 }

        
          10
        
                 @Override 
        
          public
        
        
          boolean
        
         add(E e) {

        
          11
        
                     ++addCount;

        
          12
        
        
          return
        
        
          super
        
        .add(e);

        
          13
        
                 }

        
          14
        
                 @Override 
        
          public
        
        
          boolean
        
         addAll(Collection<? 
        
          extends
        
         E> c) {

        
          15
        
                     addCount += c.size();

        
          16
        
        
          return
        
        
          super
        
        .addAll(c);

        
          17
        
                 }

        
          18
        
        
          public
        
        
          int
        
         getAddCount() {

        
          19
        
        
          return
        
         addCount;

        
          20
        
                 }

        
          21
        
             }
      

? ? ? 該子類覆蓋了HashSet中的兩個(gè)方法add和addAll,而且從表面上看也非常合理,然而他卻不能正常的工作,見下面的測試代碼:

        
          1
        
        
          public
        
        
          static
        
        
          void
        
         main(String[] args) {

        
          2
        
                 InstrumentedHashSet<String> s = 
        
          new
        
         InstrumentedHashSet<String>();

        
          3
        
                 s.addAll(Arrays.asList("Snap","Crackle","Pop"));

        
          4
        
                 System.out.println("The count of InstrumentedHashSet is " + s.getAddCount());

        
          5
        
             }

        
          6
        
        
          //
        
        
          The count of InstrumentedHashSet is 6
        
      

? ? ? 從輸出結(jié)果中可以非常清楚的看出,我們得到的結(jié)果并不是我們期望的3,而是6。這是什么原因所致呢?在HashSet的內(nèi)部,addAll方法是基于add方法來實(shí)現(xiàn)的,而HashSet的文檔中也并未列出這樣的細(xì)節(jié)說明。了解了原因之后,我們應(yīng)該取消addAll方法的覆蓋,以保證得到正確的結(jié)果。然而仍然需要指出的是,這樣的細(xì)節(jié)既然未在API文檔中予以說明,那么也就間接的表示這種未承諾的實(shí)現(xiàn)邏輯是不可依賴的,因?yàn)樵谖磥淼哪硞€(gè)版本中他們有可能會(huì)發(fā)生悄無聲息的發(fā)生變化,而我們也無法通過API文檔獲悉這些。還有一種情況是超類在未來的版本中新增了添加新元素的接口方法,因此我們在子類中也必須覆蓋這些方法,同時(shí)也要注意一些新的超類實(shí)現(xiàn)細(xì)節(jié)。由此可見,類似的繼承是非常脆弱的,那么該如何修訂我們的設(shè)計(jì)呢?答案很簡單,復(fù)合優(yōu)先于繼承,見如下代碼:

        
           1
        
        
          //
        
        
          轉(zhuǎn)發(fā)類
        
        
        
        
           2
        
        
          class
        
         ForwardingSet<E> 
        
          implements
        
         Set<E> {

        
           3
        
        
          private
        
        
          final
        
         Set<E> s;

        
           4
        
        
          public
        
         ForwardingSet(Set<E> s) {

        
           5
        
        
          this
        
        .s = s;

        
           6
        
                 }

        
           7
        
                 @Override 
        
          public
        
        
          int
        
         size() {

        
           8
        
        
          return
        
         s.size();

        
           9
        
                 }

        
          10
        
                 @Override 
        
          public
        
        
          void
        
         clear() { 

        
          11
        
                     s.clear(); 

        
          12
        
                 }

        
          13
        
                 @Override 
        
          public
        
        
          boolean
        
         add(E e) {

        
          14
        
        
          return
        
         s.add(e);

        
          15
        
                 }

        
          16
        
                 @Override 
        
          public
        
        
          boolean
        
         addAll(Collection<? 
        
          extends
        
         E> c) {

        
          17
        
        
          return
        
         s.addAll(c);

        
          18
        
                 }

        
          19
        
                 ... ...

        
          20
        
             }

        
          21
        
        
          //
        
        
          包裝類
        
        
        
        
          22
        
        
          class
        
         InstrumentedHashSet<E> 
        
          extends
        
         ForwardingSet<E> {

        
          23
        
        
          private
        
        
          int
        
         addCount = 0;

        
          24
        
        
          public
        
         InstrumentedHashSet(
        
          int
        
         initCap,
        
          float
        
         loadFactor) {

        
          25
        
        
          super
        
        (initCap,loadFactor);

        
          26
        
                 }

        
          27
        
                 @Override 
        
          public
        
        
          boolean
        
         add(E e) {

        
          28
        
                     ++addCount;

        
          29
        
        
          return
        
        
          super
        
        .add(e);

        
          30
        
                 }

        
          31
        
                 @Override 
        
          public
        
        
          boolean
        
         addAll(Collection<? 
        
          extends
        
         E> c) {

        
          32
        
                     addCount += c.size();

        
          33
        
        
          return
        
        
          super
        
        .addAll(c);

        
          34
        
                 }

        
          35
        
        
          public
        
        
          int
        
         getAddCount() {

        
          36
        
        
          return
        
         addCount;

        
          37
        
                 }

        
          38
        
             }
      

? ? ? 由上面的代碼可以看出,這種設(shè)計(jì)最大的問題就是比較瑣碎,需要將接口中的方法基于委托類重新實(shí)現(xiàn)。
? ? ? 在決定使用繼承而不是復(fù)合之間,還應(yīng)該問自己最后一組問題。對(duì)于你試圖擴(kuò)展的類,它的API中有沒有缺陷呢?如果有,你是否愿意把這些缺陷傳播到類的API中?繼承機(jī)制會(huì)把超類API中的所有缺陷傳播到子類中,而復(fù)合則允許設(shè)計(jì)新的API來隱藏這些缺陷。
?? ?
十七、要么為繼承而設(shè)計(jì),并提供文檔說明,要么就禁止繼承:

? ? ? 上一條目針對(duì)繼承將會(huì)引發(fā)的潛在問題給出了很好的解釋,本條目將繼續(xù)深化這一個(gè)設(shè)計(jì)理念,并提出一些好的建議,以便在確實(shí)需要基于繼承來設(shè)計(jì)時(shí),避免這些潛在問題的發(fā)生。
? ? ? 1)?? ?為公有方法提供更為詳細(xì)的說明文檔,這其中不僅包擴(kuò)必要的功能說明和參數(shù)描述,還要包含關(guān)鍵的實(shí)現(xiàn)細(xì)節(jié)說明,比如對(duì)其他公有方法的依賴和調(diào)用。
? ? ? 在上一條目的代碼示例中,子類同時(shí)覆蓋了HashSet的addAll和add方法,由于二者之間存在內(nèi)部的調(diào)用關(guān)系,而API文檔中并沒有給出詳細(xì)的說明,因而子類的覆蓋方法并沒有得到期望的結(jié)果。
? ? ? 2)?? ?在超類中盡可能避免公有方法之間的相互調(diào)用。
?? ?? HashSet.addAll和HashSet.add給我們提供了一個(gè)很好的案例,然而這并不表示HashSet的設(shè)計(jì)和實(shí)現(xiàn)是有問題的,我們只能說HashSet不是為了繼承而設(shè)計(jì)的類。在實(shí)際的開發(fā)中,如果確實(shí)有這樣的需要又該如何呢?很簡單,將公用的代碼提取(extract)到一個(gè)私有的幫助方法中,再在其他的公有方法中調(diào)用該幫助方法。
? ? ? 3)?? ?可以采用設(shè)計(jì)模式中模板模式的設(shè)計(jì)技巧,在超類中將需要被覆蓋的方法設(shè)定為protected級(jí)別。
? ? ? 在采用這種方式設(shè)計(jì)超類時(shí),還需要額外考慮的是哪些域字段也同時(shí)需要被設(shè)定為protected級(jí)別,以保證子類在覆蓋protected方法時(shí),可以得到必要的狀態(tài)信息。
? ? ? 4)?? ?不要在超類的構(gòu)造函數(shù)中調(diào)用可能被子類覆蓋的方法,如public和protected級(jí)別的域方法。
? ? ? 由于超類的初始化早于子類的初始化,如果此時(shí)調(diào)用的方法被子類覆蓋,而覆蓋的方法中又引用了子類中的域字段,這將很容易導(dǎo)致NullPointerException異常被拋出,見下例:

        
           1
        
        
          public
        
        
          class
        
         SuperClass {

        
           2
        
        
          public
        
         SuperClass() {

        
           3
        
                     overrideMe();

        
           4
        
                 }

        
           5
        
        
          public
        
        
          void
        
         overrideMe() {}

        
           6
        
             }

        
           7
        
        
          public
        
        
          final
        
        
          class
        
         SubClass 
        
          extends
        
         SuperClass {

        
           8
        
        
          private
        
        
          final
        
         Date d;

        
           9
        
                 SubClass() {

        
          10
        
                     d = 
        
          new
        
         Date();

        
          11
        
                 }

        
          12
        
                 @Override 
        
          public
        
        
          void
        
         overrideMe() {

        
          13
        
                     System.out.println(dd.getDay());

        
          14
        
                 }

        
          15
        
             }

        
          16
        
        
          public
        
        
          static
        
        
          void
        
         main(String[] args) {

        
          17
        
                 SubClass sub = 
        
          new
        
         SubClass();

        
          18
        
                 sub.overrideMe();

        
          19
        
             }
      

? ? ? 5)?? ?如果超類實(shí)現(xiàn)了Cloneable和Serializable接口,由于clone和readObject也有構(gòu)造的能力,因此在實(shí)現(xiàn)這兩個(gè)接口方法時(shí)也需要注意,不能調(diào)用子類的覆蓋方法。

十八、接口優(yōu)先于抽象類:

?? ?? 眾所周知,Java是不支持多重繼承但是可以實(shí)現(xiàn)多個(gè)接口的,而這也恰恰成為了接口優(yōu)于抽象類的一個(gè)重要因素。現(xiàn)將他們的主要差異列舉如下:
? ? ? 1)?? ?現(xiàn)有的類可以很容易被更新,以實(shí)現(xiàn)新的接口。
? ? ? 如果現(xiàn)存的類并不具備某些功能,如比較和序列化,那么我們可以直接修改該類的定義分別實(shí)現(xiàn)Comparable和Serializable接口。倘若Comparable和Serializable不是接口而是抽象類,那么同時(shí)繼承兩個(gè)抽象類是Java語法規(guī)則所不允許的,如果當(dāng)前類已經(jīng)繼承自某個(gè)超類了,那么他將無法再擴(kuò)展任何新的超類。
? ? ? 2)?? ?接口是定義mixin(混合類型)的理想選擇。
? ? ? Comparable是一個(gè)典型的mixin接口,他允許類表明他的實(shí)例可以與其他的可相互比較的對(duì)象進(jìn)行排序。這樣的接口之所以被稱為mixin,是因?yàn)樗试S任選的功能可被混合到類型的主要功能中。抽象類不能被用于定義mixin,同樣也是因?yàn)樗麄儾荒鼙桓碌浆F(xiàn)有的類中:類不可能有一個(gè)以上的超類,類層次結(jié)構(gòu)中也沒有適當(dāng)?shù)牡胤絹聿迦雖ixin。
? ? ? 3)?? ?接口允許我們構(gòu)造非層次結(jié)構(gòu)的類型框架。
? ? ? 由于我們可以為任何已有類添加新的接口,而無需考慮他當(dāng)前所在框架中的類層次關(guān)系,這樣便給功能的擴(kuò)展帶來了極大的靈活性,也減少了對(duì)已有類層次的沖擊。如:

        
          1
        
        
          public
        
        
          interface
        
         Singer {  
        
          //
        
        
          歌唱家
        
        
        
        
          2
        
                 AudioClip sing(Song s);

        
          3
        
             }

        
          4
        
        
          public
        
        
          interface
        
         SongWriter {  
        
          //
        
        
          作曲家
        
        
        
        
          5
        
                 Song compose(
        
          boolean
        
         hit);

        
          6
        
             }
      

? ? ? 在現(xiàn)實(shí)生活中,有些歌唱家本身也是作曲家。因?yàn)槲覀冞@里是通過接口來定義這兩個(gè)角色的,所有同時(shí)實(shí)現(xiàn)他們是完全可能的。甚至可以再提供一個(gè)接口擴(kuò)展自這兩個(gè)接口,并提供新的方法,如:

        
          1
        
        
          public
        
        
          interface
        
         SingerWriter 
        
          extends
        
         Singer, SongWriter {

        
          2
        
                 AudioClip strum();

        
          3
        
        
          void
        
         actSensitive();

        
          4
        
             }
      

?? ?? 試想一下,如果將Singer和SongWriter定義為抽象類,那么完成這一擴(kuò)展就會(huì)是非常浩大的工程,甚至可能造成"組合爆炸"的現(xiàn)象。
? ? ? 我們已經(jīng)列舉出了一些接口和抽象類之間的重要差異,下面我們還可以了解一下如何組合使用接口和抽象類,以便他們能為我們設(shè)計(jì)的框架帶來更好的擴(kuò)展性和層級(jí)結(jié)構(gòu)。在Java的Collections Framework中存在一組被稱為"骨架實(shí)現(xiàn)"(skeletal implementation)的抽象類,如AbstractCollection、AbstractSet和AbstractList等。如果設(shè)計(jì)得當(dāng),骨架實(shí)現(xiàn)可以使程序員很容易的提供他們自己的接口實(shí)現(xiàn)。這種組合還可以讓我們在設(shè)計(jì)自己的類時(shí),根據(jù)實(shí)際情況選擇是直接實(shí)現(xiàn)接口,還是擴(kuò)展該抽象類。和接口相比,骨架實(shí)現(xiàn)類還存在一個(gè)非常明顯的優(yōu)勢,既如果今后為該骨架實(shí)現(xiàn)類提供新的方法,并提供了默認(rèn)的實(shí)現(xiàn),那么他的所有子類均不會(huì)受到影響,而接口則不同,由于接口不能提供任何方法實(shí)現(xiàn),因此他所有的實(shí)現(xiàn)類必須進(jìn)行修改,為接口中新增的方法提供自己的實(shí)現(xiàn),否則將無法通過編譯。
? ? ? 簡而言之,接口通常是定義允許多個(gè)實(shí)現(xiàn)的類型的最佳途徑。這條規(guī)則有個(gè)例外,即當(dāng)演變的容易性比靈活性更為重要的時(shí)候。在這種情況下,應(yīng)該使用抽象類來定義類型,但前提是必須理解并且可以接受這些局限性。如果你導(dǎo)出了一個(gè)重要的接口,就應(yīng)該堅(jiān)決考慮同時(shí)提供骨架實(shí)現(xiàn)類。
?? ?
十九、接口只用于定義類型:

? ? ? 當(dāng)類實(shí)現(xiàn)接口時(shí),接口就充當(dāng)可以引用這個(gè)類的實(shí)例的類型。因此,類實(shí)現(xiàn)了接口,就表明客戶端可以對(duì)這個(gè)類的實(shí)例實(shí)施某些動(dòng)作。為了任何其他目的定義接口是不恰當(dāng)?shù)摹H鐚?shí)現(xiàn)Comparable接口的類,表明他可以存放在排序的集合中,之后再從集合中將存入的對(duì)象有序的讀出,而實(shí)現(xiàn)Serializable接口的類,表明該類的對(duì)象具有序列化的能力。類似的接口在JDK中大量存在。
?? ?
二十、類層次優(yōu)于標(biāo)簽類:

?? ?? 這里先給出標(biāo)簽類的示例代碼:

        
           1
        
        
          class
        
         Figure {

        
           2
        
        
          enum
        
         Shape { RECT,CIRCLE };

        
           3
        
        
          final
        
         Shape s;  
        
          //
        
        
          標(biāo)簽域字段,標(biāo)識(shí)當(dāng)前Figure對(duì)象的實(shí)際類型RECT或CIRCLE。
        
        
        
        
           4
        
        
          double
        
         length;  
        
          //
        
        
          length和width均為RECT形狀的專有域字段
        
        
        
        
           5
        
        
          double
        
         width;

        
           6
        
        
          double
        
         radius;    
        
          //
        
        
          radius是CIRCLE的專有域字段
        
        
        
        
           7
        
                 Figure(
        
          double
        
         radius) {                    
        
          //
        
        
          專為生成CIRCLE對(duì)象的構(gòu)造函數(shù)
        
        
        
        
           8
        
                     s = Shape.CIRCLE;

        
           9
        
        
          this
        
        .radius = radius;

        
          10
        
                 }

        
          11
        
                 Figure(
        
          double
        
         length,
        
          double
        
         width) {    
        
          //
        
        
          專為生成RECT對(duì)象的構(gòu)造函數(shù)
        
        
        
        
          12
        
                     s = Shape.RECT;

        
          13
        
        
          this
        
        .length = length;

        
          14
        
        
          this
        
        .width = width;

        
          15
        
                 }

        
          16
        
        
          double
        
         area() {

        
          17
        
        
          switch
        
         (s) {                        
        
          //
        
        
          存在大量的case判斷來確定實(shí)際的對(duì)象類型。
        
        
        
        
          18
        
        
          case
        
         RECT:

        
          19
        
        
          return
        
         length * width;

        
          20
        
        
          case
        
         CIRCLE:

        
          21
        
        
          return
        
         Math.PI * (radius * radius);

        
          22
        
        
          default
        
        :

        
          23
        
        
          throw
        
        
          new
        
         AssertionError();

        
          24
        
                     }

        
          25
        
                 }

        
          26
        
             }
      

?? ?? 像Figure這樣的類通常被我們定義為標(biāo)簽類,他實(shí)際包含多個(gè)不同類的邏輯,其中每個(gè)類都有自己專有的域字段和類型標(biāo)識(shí),然而他們又都同屬于一個(gè)標(biāo)簽類,因此被混亂的定義在一起。在執(zhí)行真正的功能邏輯時(shí),如area(),他們又不得不通過case語句再重新進(jìn)行劃分。現(xiàn)在我們總結(jié)一下標(biāo)簽類將會(huì)給我們的程序帶來哪些負(fù)面影響。
? ? ? 1.?? ?不同類型實(shí)例要求的域字段被定義在同一個(gè)類中,不僅顯得混亂,而且在構(gòu)造新對(duì)象實(shí)例時(shí),也會(huì)加大內(nèi)存的開銷。
? ? ? 2.?? ?初始化不統(tǒng)一,從上面的代碼中已經(jīng)可以看出,在專為創(chuàng)建CIRCLE對(duì)象的構(gòu)造函數(shù)中,并沒有提供length和width的初始化功能,而是借助了JVM的缺省初始化。這樣會(huì)給程序今后的運(yùn)行帶來潛在的失敗風(fēng)險(xiǎn)。
? ? ? 3.?? ?由于沒有在構(gòu)造函數(shù)中初始化所有的域字段,因此不能將所有的域字段定義為final的,這樣該類將有可能成為可變類。
? ? ? 4.?? ?大量的swtich--case語句,在今后添加新類型的時(shí)候,不得不修改area方法,這樣便會(huì)引發(fā)因誤修改而造成錯(cuò)誤的風(fēng)險(xiǎn)。順便說一下,這一點(diǎn)可以被看做《敏捷軟件開發(fā)》中OCP原則的反面典型。
? ? ? 那么我們需要通過什么方法來解決這樣的問題呢?該條目給出了明確的答案:利用Java語句提供的繼承功能。見下面的代碼:

        
           1
        
        
          abstract
        
        
          class
        
         Figure {

        
           2
        
        
          abstract
        
        
          double
        
         area();

        
           3
        
             }

        
           4
        
        
          class
        
         Circle 
        
          extends
        
         Figure {

        
           5
        
        
          final
        
        
          double
        
         radius;

        
           6
        
                 Circle(
        
          double
        
         radius) {

        
           7
        
        
          this
        
        .radius = radius;

        
           8
        
                 }

        
           9
        
        
          double
        
         area() {

        
          10
        
        
          return
        
         Math.PI * (radius * radius);

        
          11
        
                 }

        
          12
        
             }

        
          13
        
        
          class
        
         Rectangle 
        
          extends
        
         Figure {

        
          14
        
        
          final
        
        
          double
        
         length;

        
          15
        
        
          final
        
        
          double
        
         width;

        
          16
        
                 Rectangle(
        
          double
        
         length,
        
          double
        
         width) {

        
          17
        
        
          this
        
        .length = length;

        
          18
        
        
          this
        
        .width = width;

        
          19
        
                 }

        
          20
        
        
          double
        
         area() {

        
          21
        
        
          return
        
         length * width;

        
          22
        
                 }

        
          23
        
             }
      

? ? ? 現(xiàn)在我們?yōu)槊糠N標(biāo)簽類型都定義了不同的子類,可以明顯看出,這種基于類層次的設(shè)計(jì)規(guī)避了標(biāo)簽類的所有問題,同時(shí)也大大提供了程序的可讀性和可擴(kuò)展性,如:

        
          1
        
        
          class
        
         Square 
        
          extends
        
         Rectangle {

        
          2
        
                 Square(
        
          double
        
         side) {

        
          3
        
        
          super
        
        (side,side);

        
          4
        
                 }

        
          5
        
             }
      

? ? ? 現(xiàn)在我們新增了正方形類,而我們所需要做的僅僅是繼承Rectangle類。
? ? ? 簡而言之,標(biāo)簽類很少有適用的場景。當(dāng)你想要編寫一個(gè)包含顯式標(biāo)簽域的類時(shí),應(yīng)該考慮一下,這個(gè)標(biāo)簽是否可以被取消,這個(gè)類是否可以用類層次來代替。當(dāng)你遇到一個(gè)包含標(biāo)簽域的現(xiàn)有類時(shí),就要考慮將它重構(gòu)到一個(gè)層次結(jié)構(gòu)中去。
?? ?
二十一、用函數(shù)對(duì)象表示策略:

? ? ? 函數(shù)對(duì)象可以簡單的理解為C語言中的回調(diào)函數(shù),但是我想他更加類似于C++中的仿函數(shù)對(duì)象。仿函數(shù)對(duì)象在C++的標(biāo)準(zhǔn)庫中(STL)有著廣泛的應(yīng)用,如std::less等。在Java中并未提供這樣的語法規(guī)則,因此他們在實(shí)現(xiàn)技巧上確實(shí)存在一定的差異,然而設(shè)計(jì)理念卻是完全一致的。下面是該條目中對(duì)函數(shù)對(duì)象的描述:
? ? ? Java沒有提供函數(shù)指針,但是可以用對(duì)象引用實(shí)現(xiàn)統(tǒng)一的功能。調(diào)用對(duì)象上的方法通常是執(zhí)行該對(duì)象(that Object)上的某項(xiàng)操作。然而,我們也可能定義這樣一種對(duì)象,它的方法執(zhí)行其他對(duì)象(other Objects)上的操作。如果一個(gè)類僅僅導(dǎo)出這樣的一個(gè)方法,它的實(shí)例實(shí)際上就等同于一個(gè)指向該方法的指針。這樣的實(shí)例被稱為函數(shù)對(duì)象(Function Object),如JDK中Comparator,我們可以將該對(duì)象看做是實(shí)現(xiàn)兩個(gè)對(duì)象之間進(jìn)行比較的"具體策略對(duì)象",如:

        
          1
        
        
          class
        
         StringLengthComparator {

        
          2
        
        
          public
        
        
          int
        
         compare(String s1,String s2) {

        
          3
        
        
          return
        
         s1.length() - s2.length();

        
          4
        
                 }

        
          5
        
             }
      

? ? ? 這種對(duì)象自身并不包含任何域字段,其所有實(shí)例在功能上都是等價(jià)的,因此可以看作為無狀態(tài)的對(duì)象。這樣為了提供系統(tǒng)的性能,避免不必要的對(duì)象創(chuàng)建開銷,我們可以將該類定義為Singleton對(duì)象,如:

        
          1
        
        
          class
        
         StringLengthComparator {

        
          2
        
        
          private
        
         StringLengthComparator() {}    
        
          //
        
        
          禁止外部實(shí)例化該類
        
        
        
        
          3
        
        
          public
        
        
          static
        
        
          final
        
         StringLengthComparator INSTANCE = 
        
          new
        
         StringLengthComparator();

        
          4
        
        
          public
        
        
          int
        
         compare(String s1,String s2) {

        
          5
        
        
          return
        
         s1.length() - s2.length();

        
          6
        
                 }

        
          7
        
             }
      

?? ?? StringLengthComparator類的定義極大的限制了參數(shù)的類型,這樣客戶端也無法再傳遞任何其他的比較策略。為了修正這一問題,我們需要讓該類成為Comparator<T>接口的實(shí)現(xiàn)類,由于Comparator<T>是泛型類,因此我們可以隨時(shí)替換策略對(duì)象的參數(shù)類型,如:

        
          1
        
        
          class
        
         StringLengthComparator 
        
          implements
        
         Comparator<String> {

        
          2
        
        
          public
        
        
          int
        
         compare(String s1,String s2) {

        
          3
        
        
          return
        
         s1.length() - s2.length();

        
          4
        
                 }

        
          5
        
             }
      

? ? ? 簡而言之,函數(shù)指針的主要用途就是實(shí)現(xiàn)策略模式。為了在Java中實(shí)現(xiàn)這種模式,要聲明一個(gè)接口來表示策略,并且為每個(gè)具體策略聲明一個(gè)實(shí)現(xiàn)了該接口的類。當(dāng)一個(gè)具體策略只被使用一次時(shí),可以考慮使用匿名類來聲明和實(shí)例化這個(gè)具體的策略類。當(dāng)一個(gè)具體策略是設(shè)計(jì)用來重復(fù)使用的時(shí)候,他的類通常就要被實(shí)現(xiàn)為私有的靜態(tài)成員類,并通過公有的靜態(tài)final域被導(dǎo)出,其類型為該策略接口。
?? ?
二十二、優(yōu)先考慮靜態(tài)成員類:

? ? ? 在Java中嵌套類主要分為四種類型,下面給出這四種類型的應(yīng)用場景。
? ? ? 1.?? ?靜態(tài)成員類:?? ??? ?
? ? ? 靜態(tài)成員類可以看做外部類的公有輔助類,僅當(dāng)與它的外部類一起使用時(shí)才有意義。例如,考慮一個(gè)枚舉,它描述了計(jì)算器支持的各種操作。Operation枚舉應(yīng)該是Calculator類的公有靜態(tài)成員類,然后,Calculator類的客戶端就可以用諸如Calculator.Operation.PLUS和Calculator.Operation.MINUS這樣的名稱來引用這些操作。
? ? ? 2.?? ?非靜態(tài)成員類:
? ? ? 一種常見的用法是定義一個(gè)Adapter,它允許外部類的實(shí)例被看做是另一個(gè)不相關(guān)的類的實(shí)例。如Map接口的實(shí)現(xiàn)往往使用非靜態(tài)成員類來實(shí)現(xiàn)它們的集合視圖,這些集合視圖是由Map的keySet、entrySet和Values方法返回的。
? ? ? 從語法上講,靜態(tài)成員類和非靜態(tài)成員類之間唯一的區(qū)別是,靜態(tài)成員類的聲明中包含了static修飾符,盡管語法相似,但實(shí)際應(yīng)用卻是大相徑庭。每個(gè)非靜態(tài)成員類的實(shí)例中都隱含一個(gè)外部類的對(duì)象實(shí)例,在非靜態(tài)成員類的實(shí)例方法內(nèi)部,可以調(diào)用外圍實(shí)例的方法。如果嵌套類的實(shí)例可以在它的外圍類的實(shí)例之外獨(dú)立存在,這個(gè)嵌套類就必須是靜態(tài)成員類。由于靜態(tài)成員類中并不包含外部類實(shí)例的對(duì)象引用,因此在創(chuàng)建時(shí)減少了內(nèi)存開銷。
? ? ? 3.?? ?匿名類:
? ? ? 匿名類沒有自己的類名稱,也不是外圍類的一個(gè)成員。匿名類可以出現(xiàn)在代碼中任何允許存在表達(dá)式的地方。然而匿名類的適用性受到諸多限制,如不能執(zhí)行instanceof測試,或者任何需要類名稱的其他事情。我們也無法讓匿名類實(shí)現(xiàn)多個(gè)接口,當(dāng)然也不能直接訪問其任何成員。最后需要說的是,建議匿名類的代碼盡量短小,否則會(huì)影響程序的可讀性。
? ? ? 匿名類在很多時(shí)候可以用作函數(shù)對(duì)象。
? ? ? 4.?? ?局部類:
? ? ? 是四種嵌套類中最少使用的類,在任何"可以聲明局部變量"的地方,都可以聲明局部類,并且局部類也遵守同樣的作用域規(guī)則。

Effective Java (類和接口)


更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主

微信掃碼或搜索:z360901061

微信掃一掃加我為好友

QQ號(hào)聯(lián)系: 360901061

您的支持是博主寫作最大的動(dòng)力,如果您喜歡我的文章,感覺我的文章對(duì)您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點(diǎn)擊下面給點(diǎn)支持吧,站長非常感激您!手機(jī)微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點(diǎn)擊微信右上角掃一掃功能,選擇支付二維碼完成支付。

【本文對(duì)您有幫助就好】

您的支持是博主寫作最大的動(dòng)力,如果您喜歡我的文章,感覺我的文章對(duì)您有幫助,請用微信掃描上面二維碼支持博主2元、5元、10元、自定義金額等您想捐的金額吧,站長會(huì)非常 感謝您的哦!!!

發(fā)表我的評(píng)論
最新評(píng)論 總共0條評(píng)論
主站蜘蛛池模板: 欧美午夜精品一区二区三区 | 97精品视频在线观看 | 日韩欧美视频一区二区在线观看 | 日韩一区国产二区欧美三 | 久草丁香 | 久久久久久久国产视频 | 婷婷色人阁 | 天天操夜夜爽 | 国产高清在线a视频大全凹凸 | 国产精品四虎在线观看免费 | 老司机午夜免费福利 | 亚洲另类中文字幕 | 香焦视频在线观看黄 | 亚洲欧美另类在线观看 | 欧美日韩视频在线第一区 | 精品不卡一区中文字幕 | 青草免费视频 | 蜜桃视频黄色 | 国产理论自拍 | 欧美另类综合 | 免费观看毛片 | 人人97| 伊人第一页 | 久久国产精品范冰啊 | 波多野结衣视频一区二区 | 久久www免费人成看片色多多 | aaaa日本| 久久视精品 | 99re6在线视频免费精品 | 久久频 | 国产精品欧美韩国日本久久 | 亚洲综合区小说区激情区噜噜 | 中文字幕专区 | 久草在线视频中文 | 国产玖玖视频 | 99视频在线观看免费 | 亚洲精品免费日日日夜夜夜夜 | 天天夜碰日日摸日日澡 | 婷婷在线视频国产综合 | 国产精品一区二区三 | 男女交黄 |