第13條:使類和成員的可訪問性最小化

區別設計良好的模塊和設計很差的模塊,最重要的因素在於,這個模塊對於外部的其餘模塊而言,是否隱藏其內部數據和其餘實現細節。設計良好的模塊會隱藏全部的實現細節,把它的API於它的實現清晰地隔離開來。而後,模塊之間經過它們的API進行通訊,一個模塊不須要知道其餘模塊的內部工做狀況,這個概念稱爲信息隱藏或封裝。
使類和成員的可訪問性最小化能夠有效的解除系統中各個模塊的耦合度、實現每一個模塊的獨立開發、使得系統更加的可維護,更加的健壯。
對於頂層的(非嵌套的)類和接口,只有兩種可能的訪問級別,包級私有的和公有的。若是類或者接口能夠被作成包級私有,就應該被作成包級私有,這樣它實際上成了包的實現的一部分,而不是該包導出的API的一部分,在之後的發行版本中,能夠對它進行修改、替換、刪除,而無需擔憂影響現有的客戶端程序。若是被作成公有的,就有責任永遠支持它,以保持兼容性。
若是一個包級私有的頂層的類(或者接口)只是在某一個類的內部能夠被用到,就應該考慮使它成爲惟一使用它的那個類的私有嵌套類。這樣能夠進一步縮小可訪問範圍。api

對於成員(域、方法、嵌套類和嵌套接口)有四種可能的訪問級別:
  私有的(private)——只有在聲明該成員的頂層類內部才能夠訪問這個成員。
  包級私有的(package-private)——聲明該成員的包內部的任何類均可以訪問這個成員。若是沒有爲成員指定訪問修飾符,就採用這個訪問級別,「缺省」
  受保護的(protected)——聲明該成員的子類能夠訪問這個成員(即便不在同一包),而且聲明該成員的包內部任何類也能夠訪問這個成員
  公有的(public)——在任何地方均可以訪問該成員數組

私有成員和包級成員都是一個類的實現中的一部分,通常不會影響它導出的API,然而,若是類實現了Serializable接口,這些域就有可能被「泄漏」到導出的API中。
有一條規則限制了下降方法的可訪問性的能力,若是方法覆蓋了超類的一個方法,子類的訪問級別不容許低於超類中的訪問級別。這樣能夠確保任何可以使用超類的實例的地方均可以使用子類的實例。好比,若是一個類實現了一個接口,那麼接口中全部的類方法在這個類中也都必須被聲明爲公有的(接口的全部方法隱含公有訪問級別)。
實例域決不能是公有的。若是域是非final,或者是一個指向可變對象的final引用,那麼一旦使這個域成爲公有的,就放棄了對存儲在這個域中的值進行限制的能力。所以,包含公有可變域的類不是線程安全的。即便域是final的,而且引用不可變的對象,當把這個域變成公有的時候,也就放棄了「切換到一種新的內部數據表示法」的靈活性(由於公有的是導出API,必須始終堅持這種數據表示,保證兼容)。
安全

雖然實例域決不能是公有的,但有一種狀況是例外的,假設常量構成了類提供的整個抽象中的一部分,咱們是能夠經過公有的靜態final域來暴露這些常量,可是,若是final域包含可變對象的引用,它便具備非final域全部的缺點,雖然引用的自己是不能修改的,可是它所引用的對象卻能夠在客戶端被修改,這是很嚴重的安全後果,例以下面性能

public static final Person[] VALUES = new Person[]{ new Person("aa", 21, 'M'),new Person("c", 11, 'M'),new Person("d", 31, 'M') };

雖然咱們不能對VALUES中Person的引用進行修改,可是咱們卻能夠修改引用指向Person對象中的屬性值,如VALUES[0].setName("修改了名字");那麼該如何作纔會避免這種安全漏洞呢,修正方法有兩種,下面咱們來看下。測試

  1.使公有數組變成私有的,並增長一個公有的不可變列表:spa

private static final Person[] PRIVATE_VALUES = new Person[]{ new Person("aa", 21, 'M'),new Person("c", 11, 'M'),new Person("d", 31, 'M') };
//unmodifiableList,生成一個不可修改的List
public static final List<Person> VALUES= Collections.unmodifiableList(Arrays.asList(PRIVATE_VALUES));

  2.使公有數組變成私有的,並添加一個公有方法,返回私有數組的一個備份:線程

private static final Person[] PRIVATE_VALUES = new Person[]{ new Person("aa", 21, 'M'),new Person("c", 11, 'M'),new Person("d", 31, 'M') };
public static final Person[] values() {
    return PRIVATE_VALUES.clone();
}

在這兩種方法選擇,得考慮客戶端可能怎麼處理這個結果,哪一種返回類型更加方便,哪一種會獲得更好的性能。設計

最後總結下如何最小化類和接口的可訪問性:
  (1)首先設計出該類須要暴露出來的api,而後將剩下的成員的設計成private類型。而後再其餘類須要訪問某些private類型的成員時,在刪掉private,使其變成包級私有。若是你發現你須要常常這樣作,那麼就請你從新設計一下這個類的api。
  (2)對於protected類型的成員,做用域是整個系統,因此,能用包訪問類型的成員的話就儘可能不要使用保護行的成員。
  (3)不能爲了測試而將包中的類或者成員變爲public類型的,最多隻能設置成包級私有類型。
  (4)實例域絕對不能是public類型的。code

相關文章
相關標籤/搜索