封裝原則倡導經過隱藏抽象的實現細節和隱藏變化等來實現關注點分離和信息隱藏。算法
以汽車爲例,咱們並不須要瞭解發動機的原理就能夠開車。這準確描繪了封裝原則的做用:用戶無需知道抽象(汽車)的細節,此外,封裝原則還讓抽象可以隱藏實現細節的變化。發動機是汽油發動機仍是柴油發動機並不會對咱們開車形成影響。編程
隱藏實現細節c#
抽象向客戶端程序只暴露其提供的功能,而將實現方式隱藏起來。實現方式(即實現細節)包含抽象的內部表示(如抽象使用的數據成員和數據結構)以及有關方法是如何實現的細節(如方法使用的算法)。數據結構
隱藏變化post
隱藏類型或實現結構的實現變化。經過隱藏變化,更容易在不給客戶端程序帶來太大影響的狀況下修改抽象的實現。性能
咱們這篇博客主要講解分析不充分的封裝壞味,對於其它封裝壞味將在後面的博客講解分析。單元測試
對於抽象的一個或多個成員,聲明的訪問權限超過了實際需求時,將致使這種壞味。這種壞味的極端表現形式是,存在一些用全局變量、全局數據結構等表示的全局狀態,整個軟件系統的全部抽象均可以訪問它們。學習
封裝的主要意圖是將接口和實現分離,以便可以幾乎獨立地修改它們。這種關注點分離讓客戶端程序只依賴於抽象的接口,從而可以對它們隱藏實現細節。若是暴露了實現細節,將致使抽象和客戶端緊密耦合。這是不可取的,每當修改抽象的實現細節時,都將影響客戶端程序。提供超過須要的訪問權限可能向客戶端程序暴露實現細節,這違反了「隱藏原則」。測試
爲了方便測試,開發人員經常將抽象的私有方法改爲公有的。因爲私有方法涉及抽象的實現細節,將其改成公有將破壞抽象的封裝。this
咱們都知道代碼的可測試性是衡量代碼質量的一個重要指標。若是編寫的代碼沒法進行單元測試,代碼的質量就沒法獲得保證。在有些狀況下,代碼沒法編寫測試是能夠進行代碼修改的,咱們稱之爲重構。可是由於訪問權限修改代碼不在這些狀況下,這樣作反而會破壞代碼的封裝。能夠藉助反射實現低訪問權限成員的測試。
以全局變量的方式暴露多個抽象須要使用的數據,從而致使這種壞味。
/// <summary> /// 消息發佈類 /// </summary> public class Publisher { /// <summary> /// 頻道號 範圍1-100 /// </summary> public int channel; /// <summary> /// 建立一個特定頻道的發佈者對象 /// </summary> /// <param name="channel">頻道號 範圍1-100</param> public Publisher(int channel) { this.channel = channel; } public vois Publish(string message) { //向頻道channel發佈消息message } }
上面代碼示例就是不充分的封裝的典型,頻道號變量channel被設置爲public是不合適的,由於建立消息發佈對象時就已經指定發佈的頻道號,channel被設置爲public,頻道號在客戶端使用的時候就能夠隨意的被訪問修改,這樣客戶端就會了解消息發佈類的內部實現,形成了直接依賴,違反了「高內聚,低耦合」原則。這樣每當修改內部實現時都會對客戶端形成影響。更重要的一點是頻道號變量channel是有範圍限定的(1-100),客戶端使用的時候隨意的修改channel,可能會形成channel越界的錯誤。因此正確的作法是將channel變量設置爲私有的,而且爲其提供合適的存取器方法。
重構後的代碼實現:
/// <summary> /// 消息發佈類 /// </summary> public class Publisher { /// <summary> /// 頻道號 範圍1-100 /// </summary> private int channel; /// <summary> /// channel賦值,支持範圍限定 /// </summary> /// <param name="channel">頻道號 範圍1-100</param> public void SetChannel(int channel) { if(channel < 1 || channel > 100) { throw new ArgumentOutOfRangeException("超出頻道號 範圍1-100"); } this.channel = channel; } /// <summary> /// 建立一個特定頻道的發佈者對象 /// </summary> /// <param name="channel">頻道號 範圍1-100</param> public Publisher(int channel) { SetChannel(channel); } public vois Publish(string message) { //向頻道channel發佈消息message } }
還有一種極端表現形式:全局變量。對於全局變量,存在兩種不一樣的情形。
對於第一種情形,要進行重構,能夠經過參數傳遞必要的變量。
對於第二種情形,要進行重構,能夠根據其承擔的責任建立合適的抽象,並在這些抽象中封裝原來的全局變量,這樣客戶端就會使用這些抽象,而不是直接使用全局變量。
存在不充分的封裝壞味時,會使代碼的可重用性大打折扣,由於客戶程序直接依賴你們均可以訪問的狀態,致使難以在其它地方重用客戶程序。
抽象容許直接訪問其數據成員時,確保數據和整個抽象完整性的職責由抽象轉移到了各個客戶程序。增長了代碼運行階段發生問題的可能性。
相對於使用存取器方法控制對變量訪問修改帶來的好處,使用存取器方法帶來的性能開銷能夠忽略不計。
參考:《軟件設計重構》