.NET 设计规范--.NET约定、惯用法与模式--6.为扩展性而设计
6.1 扩展机制
6.1.1 非密封类
密封类即无法派生子类,也无法扩展。与此相反,非密封类可以派生子类进行扩展
//string cannot be inherited from
public sealed class String{}
//TraceSource can be inherited from
public class TraceSource{}
正因为如此,如果需要既简单有开销不大的扩展方法,那么没有声明任何虚成员或保护成员的非密封类时不错的选择。
考虑用不包含任何虚成员或保护成员的非密封类来为框架提供扩展性,这种方法的开销不高,用户也喜欢。
6.1.2 保护成员
保护成员本身并不能提供任何的扩展性,但它们能够加强派生子类这一扩展机制。
考虑将保护成员用于高级定制
要在对安全性。文档及兼容性进行分析时,把非密封类中保护成员作为公有成员来对待
6.1.3 事件与回调函数
回调函数是一种扩展机制,它使框架能够通过委托来调用用户代码,这些委托通常是作为方法的参数传递给框架的。
事件是一种特殊的回调函数,它为用户提供了一致的语法,使用户能够非常方便的把委托提供给框架
考虑使用回调函数来让框架能够执行用户提供的代码
考虑用事件来让用户定制框架的行为,这样就不需要用用户对面向对象设计有深入了解。
要游戏使用事件,而不是简单的回调函数,其原因在于广大开发人员更熟悉事件,而且事件与Visual Studio的statement completion特性集成在一起。
避免在对性能要求很高的API中使用回调函数。
要理解在调用委托时可以执行任何代码,这可能会引起安全性,正确行及兼容性的问题。
6.1.4 虚成员
与回调函数相比,虚成员的主要的缺点在于它的行为只能静态修改(在编译时修改),而回调函数的行为则可以动态修改(在运行时修改)。
不要使用虚成员,除非有合适的理由,同时你对设计、测试及维护虚成员的开销有清楚的认识。
在以扩展性为 名义做决定前,请确保你完全理解了自己对扩展的需求。
考虑只有在绝对必须的时候才用虚成员提供扩展性,并使用Template Method模式。
要优先使用受保护的虚成员,而不是公有虚成员。公有虚成员应该通过调用受保护的虚成员的方式来提供扩展性。
类的公有成员应该为类的直接使用者提供正确的功能集。虚成员的设计目的是为了让子类覆盖。而受保护的访问权限是一种很好的方法,可以对其使用范围加以限制。
6.1.5 抽象(抽象类型与抽象接口)
抽象就是描述一个协定但是并不为该协定提供完整实现的类型。抽象通常实现为抽象类或接口,并带有一些参考文档,来描述实现该协定类型必须具备的那些语义。
不要提供抽象,除非为该抽象开发出具体实现并用到该抽象的API队其进行过实际测试。
要在设计抽象时谨慎地选择抽象类或是抽象接口
考虑为抽象的具体实现提供参考测试。这类测试应该告诉用于,他们是否正确实现饿了协定。
6.2 基类
我们把基类限定为这样的类:其设计目的不是为了直接使用或提供常用的抽象,而是为了让其他类通过继承类重用它的默认实现。
考虑使用基类为抽象类,即使他们并不包含任何抽象成员。这能够清楚地告诉用户,设计这些类的目的的完全是为了让用户从他们派生自己的子类。
考虑把基类与用于主要场景的类型分开,放到单独的名字空间中。根据定义,基类是为了高级扩展而设计的,因此大多数用户对它们并不感兴趣。
避免在命名基类时使用“Base”后缀,如果该类会用于公用API。
6.3 密封
密封是一种阻止扩展的有效机制。开发人员既可以密封整个类,也可以密封类中的单个成员。密封整个类使用户不能自该类继承,而密封一个成员则使用户不能覆盖该成员。
不要把类密封起来,除非有恰当的理由。
把类密封起来的恰当理由包括:
类为静态类
类的保护成员保存了需要高度保密的机密信息
类继承了许多虚成员,把这些成员一个一个都密封起来的代价太高,还不如把整个类都密封起来。
类时attribute,需要能再运行的时候快速查找
不要在密封类中声明保护成员或虚成员
考虑在覆盖成员时将其密封