# 原則21:限制你的類型的可見性
**By D.S.Qiu**
**尊重他人的勞動,支持原創,轉載請注明出處:[http://dsqiu.iteye.com](http://dsqiu.iteye.com)**
不是每個人都需要知道每件事。你創建的類型也不是都需要 public 。只要能達到你的目的,你就應該讓你的每個類都是最小的可見性。那總是會比你認為的更小的可見性。 internal 和 private 類可以實現 public 接口。所有客戶可以訪問 private 類的 public 函數接口。
創建 public 類型太簡單不過了。而且,它經常只是一個權宜之計。很多單獨的類型你都創建為 internal 。你也通過在類里面創建 protected 或者 private 類更進一步限制可見性。當你更新時,可見性越小,整個系統改變就越小。當你修改代碼時,訪問的代碼越少,改變的地方就越少。
只需要暴露才暴露。實現 public 接口盡量訪問更少的類。你會發現 Enumerator 模式的使用貫穿 .NET 框架庫。System.Collections.Generic.List<T> 包含一個 private 類 Enumerator<T> ,它實現 IEnumerator<T> 接口:
```
// For illustration, not complete source
public class List<T> : IEnumerable<T>
{
private class Enumerator<T> : IEnumerator<T>
{
// Contains specific implementation of
// MoveNext(), Reset(), and Current.
public Enumerator(List<T> storage)
{
// elided
}
}
public IEnumerator<T> GetEnumerator()
{
return new Enumerator<T>(this);
}
// other List members.
}
```
當你寫客戶端代碼時,不需要知道 Enumerator<T> 類。你只需要知道調用 List<T> 對象是實現 IEnumerator<T> 接口的可以調用 GetEnumerator 函數。具體哪的類型就是實現上的細節。 .NET 框架設計者對其他集合類也遵循同樣的模式: Dictionary<T> 包含一個 private DictionaryEnumerator<T> ,Queue<T> 包含一個 QueueEnumerator<T> 等等。這個迭代類是 private 會有很多好處。首先,List<T> 可以代替任何實現 IEnumerator<T> 的類,并且一點都不會不明智。沒有破壞任何東西。而且 Enumerator 類不需要和 公共語言規范(CLS)的保持一致。 public 接口才需要。你使用 Enumerator 類不需要知道實現類的詳細細節。
創建 internal 類是一個經常被忽略限制類作用域的方法。默認情況下,大多數程序員總是創建 public 類,而沒有想過其他替代方案。那是 VS.NET 向導的事情。不要不假思索地接受默認設置,你應該仔細考慮將要使用的新的類型。它是不是對客戶很有用的,或是它主要用在這個程序集的內部使用?
通過接口暴露你的功能,使得你更容易創建內部類而不限制其在程序集外部的使用(查看原則26)。這類型應該是公共的,還是一個聚集的接口的一個更好的方式來描述它的功能?interal 類允許你使用不同的版本的類,只要它實現了相同的接口。來看下面的例子,考慮一類驗證電話
號碼的:
```
public class PhoneValidator
{
public bool ValidateNumber(PhoneNumber ph)
{
// perform validation.
// Check for valid area code, exchange.
return true;
}
}
```
幾個月過去了,這個類還能很好的工作。后來你接到需要處理國際電話號碼的需求。上面的號碼驗證就會失敗。它只能處理 U.S. 的電話號碼。你仍需要驗證 U.S. 電話,但是你需要在同一個軟件中使用國際版本。你最好能降低不同項目之間的耦合,而不是在同一個類增加額外的函數。你可以定義一個驗證號碼的接口:
```
public interface IPhoneValidator
{
bool ValidateNumber(PhoneNumber ph);
}
```
下一步,讓已經存在的號碼驗證器類實現這個接口,并且聲明為 internal 類:
```
internal class USPhoneValidator : IPhoneValidator
{
public bool ValidateNumber(PhoneNumber ph)
{
// perform validation.
// Check for valid area code, exchange.
return true;
}
}
```
最后,你還可以創建國際號碼驗證器類:
```
internal class InternationalPhoneValidator : IPhoneValidator
{
public bool ValidateNumber(PhoneNumber ph)
{
// perform validation.
// Check international code.
// Check specific phone number rules.
return true;
}
}
```
為了完成這個功能,你需要創建恰當的基于號碼類型的類。你可以使用工廠模式達到這個目的。在程序集外,只有接口是可見的。這個類,具體于世界不同區域,只有在程序集內部可見。你可以為不同區域添加不同的驗證類而不干擾系統里任何其他程序集。通過限制類的作用域,你就已經很好地限制因更新的代碼和擴展整個系統的改變。
你也可以創建一個號碼驗證器的 public abstract 基類,包含了通用算法的實現。使用者可以通過可訪問的基類訪問 public 的功能。在這個例子中,我更喜歡使用 public 接口實現因為只有很少,共享的功能。其他情況使用 public abstract 基類會更好。無論你實現哪種方式,越少類是 public 可訪問的。
此外,越少 public 類就越少的 public 區域這將有助于覆蓋率單元測試。如果有更少的 public 類,就更少需要創建測試用例的 public 可訪問的方法。同時,如果通過接口暴露更多的公共 API ,你有自動創建一個系統,可以替代那些用于單元測試使用的備份。
你暴露給外部的那些類和接口是你的約定:你必須依靠它們。接口越混亂,你將來受到的約束就越多。暴露越少 public 類,你將來會有更多擴展和修改任何實現的選擇。
小結:
這個原則很簡單卻很重要,在軟件系統架構中相當重要,永遠記住暴露越少的 public 接口,后續你的痛苦就會越少。接口定義規范,基類實現公有功能!
歡迎各種不爽,各種噴,寫這個純屬個人愛好,秉持”分享“之德!
有關本書的其他章節翻譯請[點擊查看](/category/297763),轉載請注明出處,尊重原創!
如果您對D.S.Qiu有任何建議或意見可以在文章后面評論,或者發郵件(gd.s.qiu@gmail.com)交流,您的鼓勵和支持是我前進的動力,希望能有更多更好的分享。
轉載請在**文首**注明出處:[http://dsqiu.iteye.com/blog/2083197](/blog/2083197)
更多精彩請關注D.S.Qiu的博客和微博(ID:靜水逐風)
- 第一章 C# 語言習慣
- 原則1:使用 屬性(Poperty)代替可直接訪問的數據成員(Data Member)
- 原則2:偏愛 readonly 而不是 const
- 原則3:選擇 is 或 as 而不是強制類型轉換
- 原則4:使用條件特性(conditional attribute)代替 #if
- 原則5:總是提供 ToString()
- 原則6:理解幾個不同相等概念的關系
- 原則7:明白 GetHashCode() 的陷阱
- 原則8:優先考慮查詢語法(query syntax)而不是循環結構
- 原則9:在你的 API 中避免轉換操作
- 原則10:使用默認參數減少函數的重載
- 原則11:理解小函數的魅力
- 第二章 .NET 資源管理
- 原則12:選擇變量初始化語法(initializer)而不是賦值語句
- 原則13:使用恰當的方式對靜態成員進行初始化
- 原則14:減少重復的初始化邏輯
- 原則15:使用 using 和 try/finally 清理資源
- 原則16:避免創建不需要的對象
- 原則17:實現標準的 Dispose 模式
- 原則17:實現標準的 Dispose 模式
- 原則18:值類型和引用類型的區別
- 原則19:確保0是值類型的一個有效狀態
- 原則20:更傾向于使用不可變原子值類型
- 第三章 用 C# 表達設計
- 原則21:限制你的類型的可見性
- 原則22:選擇定義并實現接口,而不是基類
- 原則23:理解接口方法和虛函數的區別
- 原則24:使用委托來表達回調
- 原則25:實現通知的事件模式
- 原則26:避免返回類的內部對象的引用
- 原則27:總是使你的類型可序列化
- 原則28:創建大粒度的網絡服務 APIs
- 原則29:讓接口支持協變和逆變
- 第四章 和框架一起工作
- 原則30:選擇重載而不是事件處理器
- 原則31:用 IComparable&lt;T&gt; 和 IComparer&lt;T&gt; 實現排序關系
- 原則32:避免 ICloneable
- 原則33:只有基類更新處理才使用 new 修飾符
- 原則34:避免定義在基類的方法的重寫
- 原則35:理解 PLINQ 并行算法的實現
- 原則36:理解 I/O 受限制(Bound)操作 PLINQ 的使用
- 原則37:構造并行算法的異常考量
- 第五章 雜項討論
- 原則38:理解動態(Dynamic)的利與弊
- 原則39:使用動態對泛型類型參數的運行時類型的利用
- 原則40:使用動態接收匿名類型參數