欧美三区_成人在线免费观看视频_欧美极品少妇xxxxⅹ免费视频_a级毛片免费播放_鲁一鲁中文字幕久久_亚洲一级特黄

Effective Java (類和接口)

系統(tǒng) 1820 0

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

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

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

? ? ? 這個條目簡短的標題已經(jīng)非常清晰的表達了他的含義,我們這里將只是列出幾點說明:
? ? ? 1.?? ?對于公有類而言,由于存在大量的使用者,因此修改API接口將會給使用者帶來極大的不便,他們的代碼也需要隨之改變。如果公有類直接暴露了域字段,一旦今后需要針對該域字段添加必要的約束邏輯時,唯一的方法就是為該字段添加訪問器接口,而已有的使用者也將不得不更新其代碼,以避免破壞該類的內(nèi)部邏輯。
? ? ? 2.?? ?對于包級類和嵌套類,公有的域方法由于只能在包內(nèi)可以被訪問,因而修改接口不會給包的使用者帶來任何影響。
? ? ? 3.?? ?對于公有類中的final域字段,提供直接訪問方法也會帶來負面的影響,只是和非final對象相比可能會稍微好些,如final的數(shù)組對象,即便數(shù)組對象本身不能被修改,但是他所包含的數(shù)組成員還是可以被外部改動的,針對該情況建議提供API接口,在該接口中可以添加必要的驗證邏輯,以避免非法數(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)造的時候做初始化,構(gòu)造之后類的外部沒有任何方法可以修改類成員的狀態(tài),該對象在整個生命周期內(nèi)都會保持固定不變的狀態(tài),如String、Integer等。不可變類比可變類更加易于設(shè)計、實現(xiàn)和使用,而且線程安全。
? ? ? 使類成為不可變類應(yīng)遵循以下五條原則:
? ? ? 1.?? ?不要提供任何會修改對象狀態(tài)的方法;
? ? ? 2.?? ?保證類不會被擴展,既聲明為final類,或?qū)?gòu)造函數(shù)定義為私有;
?? ?? 3.?? ?使所有的域都是final的;
? ? ? 4.?? ?使所有的域都成為私有的;
? ? ? 5.?? ?確保在返回任何可變域時,返回該域的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
        
             }
      

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

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

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

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

        
        
           3
        
        
          //
        
        
          更新該值,再提供一個公有的方法返回該值。
        
        
        
        
           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中的兩個方法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方法來實現(xiàn)的,而HashSet的文檔中也并未列出這樣的細節(jié)說明。了解了原因之后,我們應(yīng)該取消addAll方法的覆蓋,以保證得到正確的結(jié)果。然而仍然需要指出的是,這樣的細節(jié)既然未在API文檔中予以說明,那么也就間接的表示這種未承諾的實現(xiàn)邏輯是不可依賴的,因為在未來的某個版本中他們有可能會發(fā)生悄無聲息的發(fā)生變化,而我們也無法通過API文檔獲悉這些。還有一種情況是超類在未來的版本中新增了添加新元素的接口方法,因此我們在子類中也必須覆蓋這些方法,同時也要注意一些新的超類實現(xiàn)細節(jié)。由此可見,類似的繼承是非常脆弱的,那么該如何修訂我們的設(shè)計呢?答案很簡單,復(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è)計最大的問題就是比較瑣碎,需要將接口中的方法基于委托類重新實現(xiàn)。
? ? ? 在決定使用繼承而不是復(fù)合之間,還應(yīng)該問自己最后一組問題。對于你試圖擴展的類,它的API中有沒有缺陷呢?如果有,你是否愿意把這些缺陷傳播到類的API中?繼承機制會把超類API中的所有缺陷傳播到子類中,而復(fù)合則允許設(shè)計新的API來隱藏這些缺陷。
?? ?
十七、要么為繼承而設(shè)計,并提供文檔說明,要么就禁止繼承:

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

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

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

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

        
          3
        
             }

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

        
          6
        
             }
      

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

        
          1
        
        
          public
        
        
          interface
        
         SingerWriter 
        
          extends
        
         Singer, SongWriter {

        
          2
        
                 AudioClip strum();

        
          3
        
        
          void
        
         actSensitive();

        
          4
        
             }
      

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

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

?? ?? 這里先給出標簽類的示例代碼:

        
           1
        
        
          class
        
         Figure {

        
           2
        
        
          enum
        
         Shape { RECT,CIRCLE };

        
           3
        
        
          final
        
         Shape s;  
        
          //
        
        
          標簽域字段,標識當前Figure對象的實際類型RECT或CIRCLE。
        
        
        
        
           4
        
        
          double
        
         length;  
        
          //
        
        
          length和width均為RECT形狀的專有域字段
        
        
        
        
           5
        
        
          double
        
         width;

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

        
           9
        
        
          this
        
        .radius = radius;

        
          10
        
                 }

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

        
          13
        
        
          this
        
        .length = length;

        
          14
        
        
          this
        
        .width = width;

        
          15
        
                 }

        
          16
        
        
          double
        
         area() {

        
          17
        
        
          switch
        
         (s) {                        
        
          //
        
        
          存在大量的case判斷來確定實際的對象類型。
        
        
        
        
          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這樣的類通常被我們定義為標簽類,他實際包含多個不同類的邏輯,其中每個類都有自己專有的域字段和類型標識,然而他們又都同屬于一個標簽類,因此被混亂的定義在一起。在執(zhí)行真正的功能邏輯時,如area(),他們又不得不通過case語句再重新進行劃分。現(xiàn)在我們總結(jié)一下標簽類將會給我們的程序帶來哪些負面影響。
? ? ? 1.?? ?不同類型實例要求的域字段被定義在同一個類中,不僅顯得混亂,而且在構(gòu)造新對象實例時,也會加大內(nèi)存的開銷。
? ? ? 2.?? ?初始化不統(tǒng)一,從上面的代碼中已經(jīng)可以看出,在專為創(chuàng)建CIRCLE對象的構(gòu)造函數(shù)中,并沒有提供length和width的初始化功能,而是借助了JVM的缺省初始化。這樣會給程序今后的運行帶來潛在的失敗風(fēng)險。
? ? ? 3.?? ?由于沒有在構(gòu)造函數(shù)中初始化所有的域字段,因此不能將所有的域字段定義為final的,這樣該類將有可能成為可變類。
? ? ? 4.?? ?大量的swtich--case語句,在今后添加新類型的時候,不得不修改area方法,這樣便會引發(fā)因誤修改而造成錯誤的風(fēng)險。順便說一下,這一點可以被看做《敏捷軟件開發(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標簽類型都定義了不同的子類,可以明顯看出,這種基于類層次的設(shè)計規(guī)避了標簽類的所有問題,同時也大大提供了程序的可讀性和可擴展性,如:

        
          1
        
        
          class
        
         Square 
        
          extends
        
         Rectangle {

        
          2
        
                 Square(
        
          double
        
         side) {

        
          3
        
        
          super
        
        (side,side);

        
          4
        
                 }

        
          5
        
             }
      

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

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

        
          1
        
        
          class
        
         StringLengthComparator {

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

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

        
          4
        
                 }

        
          5
        
             }
      

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

        
          1
        
        
          class
        
         StringLengthComparator {

        
          2
        
        
          private
        
         StringLengthComparator() {}    
        
          //
        
        
          禁止外部實例化該類
        
        
        
        
          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>接口的實現(xiàn)類,由于Comparator<T>是泛型類,因此我們可以隨時替換策略對象的參數(shù)類型,如:

        
          1
        
        
          class
        
         StringLengthComparator 
        
          implements
        
         Comparator<String> {

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

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

        
          4
        
                 }

        
          5
        
             }
      

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

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

Effective Java (類和接口)


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

微信掃碼或搜索:z360901061

微信掃一掃加我為好友

QQ號聯(lián)系: 360901061

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

【本文對您有幫助就好】

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

發(fā)表我的評論
最新評論 總共0條評論
主站蜘蛛池模板: 狠狠躁夜夜躁人人爽视频 | 欧美二区三区 | 99re99| 91青青草视频在线观看 | 亚洲第一国产 | 婷婷综合色| 亚洲国产欧美在线观看 | 精一区二区 | 26uuu欧美视频在线观看 | 天天综合亚洲 | 福利片在线观看 | 91av一区| 黄色aaa视频 | 国产精品无码2021在线观看 | 国产精品视频999 | 欧美视频国产 | 亚洲欧美视频在线 | 国产精品久久久久久久 | 色婷婷久久 | 制服丝袜成人动漫 | 91偷拍精品一区二区三区 | 亚洲影视在线 | 亚洲第一天堂 | 亚洲精品美女视频 | 边摸边吃奶边做激情叫床 | 男女黄 | 成人免费一区二区三区视频软件 | 亚洲日本中文字幕区 | 欧美在线免费 | 高清一区二区亚洲欧美日韩 | 欧美午夜精品久久久久免费视 | 亚洲网站免费观看 | 亚洲性猛交xx乱 | 波多野结衣高清在线播放 | 亚洲国产精品99久久久久久久久 | 欧美精品国产第一区二区 | 98精品国产高清在线xxxx | 欧美日韩久久久 | 天天摸天天射 | 日日麻批的全部过程 | 亚洲福利 |