日韩久久久精品,亚洲精品久久久久久久久久久,亚洲欧美一区二区三区国产精品 ,一区二区福利

Effective Java (類和接口)

系統 1913 0

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

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

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

        
          2
        
        
          public
        
        
          static
        
        
          final
        
         Thing[] values() {

        
          3
        
        
          return
        
         PRIVATE_VALUES.clone();

        
          4
        
             }    
      

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

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

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

        
          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
        
             }
      

十五、使可變性最小化:

?? ?? 只在類構造的時候做初始化,構造之后類的外部沒有任何方法可以修改類成員的狀態,該對象在整個生命周期內都會保持固定不變的狀態,如String、Integer等。不可變類比可變類更加易于設計、實現和使用,而且線程安全。
? ? ? 使類成為不可變類應遵循以下五條原則:
? ? ? 1.?? ?不要提供任何會修改對象狀態的方法;
? ? ? 2.?? ?保證類不會被擴展,既聲明為final類,或將構造函數定義為私有;
?? ?? 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
        
             }
      

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

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

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

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

        
        
           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
        
      

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

        
           1
        
        
          //
        
        
          轉發類
        
        
        
        
           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
        
             }
      

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

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

十八、接口優先于抽象類:

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

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

        
          3
        
             }

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

        
          6
        
             }
      

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

        
          1
        
        
          public
        
        
          interface
        
         SingerWriter 
        
          extends
        
         Singer, SongWriter {

        
          2
        
                 AudioClip strum();

        
          3
        
        
          void
        
         actSensitive();

        
          4
        
             }
      

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

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

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

        
           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對象的構造函數
        
        
        
        
           8
        
                     s = Shape.CIRCLE;

        
           9
        
        
          this
        
        .radius = radius;

        
          10
        
                 }

        
          11
        
                 Figure(
        
          double
        
         length,
        
          double
        
         width) {    
        
          //
        
        
          專為生成RECT對象的構造函數
        
        
        
        
          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這樣的類通常被我們定義為標簽類,他實際包含多個不同類的邏輯,其中每個類都有自己專有的域字段和類型標識,然而他們又都同屬于一個標簽類,因此被混亂的定義在一起。在執行真正的功能邏輯時,如area(),他們又不得不通過case語句再重新進行劃分。現在我們總結一下標簽類將會給我們的程序帶來哪些負面影響。
? ? ? 1.?? ?不同類型實例要求的域字段被定義在同一個類中,不僅顯得混亂,而且在構造新對象實例時,也會加大內存的開銷。
? ? ? 2.?? ?初始化不統一,從上面的代碼中已經可以看出,在專為創建CIRCLE對象的構造函數中,并沒有提供length和width的初始化功能,而是借助了JVM的缺省初始化。這樣會給程序今后的運行帶來潛在的失敗風險。
? ? ? 3.?? ?由于沒有在構造函數中初始化所有的域字段,因此不能將所有的域字段定義為final的,這樣該類將有可能成為可變類。
? ? ? 4.?? ?大量的swtich--case語句,在今后添加新類型的時候,不得不修改area方法,這樣便會引發因誤修改而造成錯誤的風險。順便說一下,這一點可以被看做《敏捷軟件開發》中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
        
             }
      

? ? ? 現在我們為每種標簽類型都定義了不同的子類,可以明顯看出,這種基于類層次的設計規避了標簽類的所有問題,同時也大大提供了程序的可讀性和可擴展性,如:

        
          1
        
        
          class
        
         Square 
        
          extends
        
         Rectangle {

        
          2
        
                 Square(
        
          double
        
         side) {

        
          3
        
        
          super
        
        (side,side);

        
          4
        
                 }

        
          5
        
             }
      

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

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

        
          1
        
        
          class
        
         StringLengthComparator {

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

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

        
          4
        
                 }

        
          5
        
             }
      

? ? ? 這種對象自身并不包含任何域字段,其所有實例在功能上都是等價的,因此可以看作為無狀態的對象。這樣為了提供系統的性能,避免不必要的對象創建開銷,我們可以將該類定義為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類的定義極大的限制了參數的類型,這樣客戶端也無法再傳遞任何其他的比較策略。為了修正這一問題,我們需要讓該類成為Comparator<T>接口的實現類,由于Comparator<T>是泛型類,因此我們可以隨時替換策略對象的參數類型,如:

        
          1
        
        
          class
        
         StringLengthComparator 
        
          implements
        
         Comparator<String> {

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

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

        
          4
        
                 }

        
          5
        
             }
      

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

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

Effective Java (類和接口)


更多文章、技術交流、商務合作、聯系博主

微信掃碼或搜索:z360901061

微信掃一掃加我為好友

QQ號聯系: 360901061

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

【本文對您有幫助就好】

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

發表我的評論
最新評論 總共0條評論
主站蜘蛛池模板: 长顺县| 皮山县| 威海市| 玉山县| 水城县| 广平县| 沧州市| 乌兰浩特市| 新河县| 察隅县| 沈丘县| 瑞昌市| 金溪县| 保德县| 台州市| 滕州市| 吉首市| 武川县| 儋州市| 台东县| 鄢陵县| 怀集县| 虎林市| 水富县| 乌拉特后旗| 葵青区| 武清区| 鹤壁市| 璧山县| 彩票| 清河县| 和静县| 娱乐| 内丘县| 海淀区| 壤塘县| 岳阳市| 江津市| 平顺县| 北辰区| 怀化市|