[Effective C# 4.0 譯] 條款21:限定類型的可見性
[Effective C# 4.0 譯] 條款21:限定類型的可見性
翻譯:羅朝輝(http://www.rzrgm.cn/kesalin/)
由于調查不到位,[Effective C#]C# 4.0版本的中文版業已出版,書名為《C#高效編程》,翻譯系列不會繼續了,大約會寫些讀書筆記吧。
題記:網絡中已不乏[Effective C#]的中文翻譯版,中文版本也已出版,但是內容比較老,不是最新版(C# 4.0),這就是我翻譯該系統文章的原因之一;本人雖然胡亂碼過幾年C/C++,Java,Objective-C,但卻是C#新手,一邊翻譯一邊學習是我翻譯該系列文章的原因之二。因為是新手,錯誤疏落難免,還請各位指正。版權申明:[Effective C# 4.0 譯]系列翻譯文章僅為學習愛好之用,遵循“署名-非商業用途-保持一致”創作公用協議,請支持英文正版。
條款21:限定類型的可見性
并非所有人都需要知道所有事。也并非你創建的所以類型都需為public。你應只賦予每個類型用來完成你工作所必須的最小的可見性,通常比你能想象的還要少。內部或私有類型能實現public接口,所有客戶都可以訪問由在私有類型中聲明的public接口定義的功能
創建public類型實在是太容易了,并且,通常那樣做也是適宜的。許多獨立存在的類都應該是內部的,還可以在類中創建protected或private嵌套類來進一步限制其可見性。一個類的可見性越少,在對整個系統更新時所須做的改動就越少;可訪問一段代碼的地方越少,在對之進行修改時所須做的改動也就越少。
只暴露必須被暴露的內容,盡量通過實現public接口來減少類的可見性。可以在.NET 框架庫中找到使用迭代模式的例子,System.Collections.Generic .List<T>包含一個私有類: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>對象上調用GetEnumerator方法時所得到的是一個實現了IEnumerator<T>接口的對象就可以了。.NET框架的設計者們在實現其他集合類時也遵循了同樣的模式:Dictionary<T>包含一個私有的DictionaryEnumerator<T>類;Queue<T>包含一個私有類QueueEnumerator<T>;等等。私有的枚舉類有很多好處,首先,List<T>完全可以取代實現IEnumerator<T>接口的類型,而你不會破壞任何東西,哇,你已是一個聰明的程序員了;其次,枚舉類無須是CLS(Common Language Specification)兼容的,因為它不是public(參考條款 49),而它的public接口是兼容的。你可以使用枚舉器而不用知道實現該枚舉器的類的細節。
創建內部類是一種常被忽視的限制類范圍的手段。默認情況下,很多程序員總是創建public類,而不考慮其他替代方案。那正是VS .NET向導所做的事情。你應該仔細考慮新的類型會在什么情況下使用,而不是不假思索地接受默認方案。類型對所有客戶都有用么?或它主要是在當前程序集內部使用?
通過使用接口來暴露功能,可使你更容易地創建內部類,而不會在程序集外部限制其可使用性(參考條款 26)。類型需要為public么?或聚合一組接口來描述其功能是更好方式?使用內部類你可以用不同版本的類來替換,只要這些類實現同樣的接口。舉例來說,考慮一個驗證電話號碼的類:
public class PhoneValidator
{
public bool ValidateNumber(PhoneNumber ph)
{
// perform validation.
// Check for valid area code, exchange.
return true;
}
}
這個類很好地工作了好幾個月。這時你得到一個處理國際電話號碼的需求,先前實現的PhoneValidator不能正常工作了,因為它只處理美國電話號碼。現在你仍需驗證美國電話號碼,只不過你需要使用能處理國際號碼版本的一攬子解決方案。與其在一個類中耦合額外的功能,不如在兩個不同的類中將之解耦。你創建一個接口來驗證任意電話號碼。
public interface IPhoneValidator
{
bool ValidateNumber(PhoneNumber ph);
}
接著,修改已有的電話驗證邏輯來實現這個接口,并將它當做一個內部類:
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;
}
}
為了完成這個實現,你需要創建基于電話號碼類型的合適的類。你可以使用工廠模式來實現。在程序集外部,只有接口是可見的。那些用于驗證特定地區號碼的類,只在程序集內部可見。你可以為不同地區增加驗證類而不會影響系統中的其他程序集。通過限制類的范圍,你就限定了升級或擴展整個系統時所需修改的代碼。
你還可以為PhoneValidator創建一個public 抽象基類,它包含通用的驗證算法邏輯。客戶能夠通過可訪問的基類來訪問公共功能。在這個例子中,我更喜歡使用public 接口的實現,因為它只含有很少的公共功能。其他用戶可能更偏好public抽象基類。無論選擇哪一種方式實現,都只有很少類具有public訪問性。
此外,public類型越少,暴露的public區域就越少,從而更容易進行單元測試覆蓋。如果只有很少的public類型,那你需要測試的可訪問的public方法也就越少。同樣,如果通過接口暴露的public API越多,你就自動創建了一個系統,借助該系統你能夠用某些用于單元測試目的存根來取代這些類型。
那些公開暴露給外部世界的類和接口就是你的合約:你必須實現它。接口越混亂,前途就越渺茫。暴露的接口越少,將來你就有更多的選擇來擴展或修改任何實現。
浙公網安備 33010602011771號