[Effective C# 4.0 译] 条款21:限定类型的可见性

[Effective C# 4.0 译] 条款21:限定类型的可见性

翻译:罗朝辉(http://www.cnblogs.com/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越多,你就自动创建了一个系统,借助该系统你能够用某些用于单元测试目的存根来取代这些类型。

那些公开暴露给外部世界的类和接口就是你的合约:你必须实现它。接口越混乱,前途就越渺茫。暴露的接口越少,将来你就有更多的选择来扩展或修改任何实现。

 

posted @ 2012-03-02 09:12  飘飘白云  阅读(555)  评论(0编辑  收藏  举报
本博客遵循 Creative Commons License “署名-非商业用途-保持一致”创作共用协议。 与我联系