5.1. 扩展机制
5.1.1. 非密封类
密封类( sealed class)既无法派生子类,也无法扩展。与此相反,非密封类(unsealed class)可以派生子类并进行扩展。
ü 考虑用不包含任何虚成员或保护成员的非密封类来为框架提供扩展性,这种方法的开销不高,用户也喜欢。
5.1.2. 保护成员
保护成员本身不能提供任何的扩展性,但他们能加强派生子类这一扩展机制。可以用他们来暴露高级的定制选项,同时可以避免不必要地是公用接口复杂化。
ü 考虑将保护成员用于高级定制。
ü 要在对安全性、文档及兼容性进行分析时,把非密封类保护成员作为公有成员来对待。
5.1.3. 事件与回调函数
回调函数是一种扩展机制,它是框架能够实用委托来调用用户代码,这些委托是作为方法的参数传给框架的。
事件是一种特殊的回调函数,它为用户提供一致的语法,使用户能够非常方便地把委托提供给框架。
Timer timer = new Timer(1000);
timer.Elapsed += delegate
{…}
与虚成员相比,回调函数可以用来提供相当强的扩展性。同时由于不需要对面向对象设计有非常深入的了解,因此回调函数和事件能够为更广泛的丌发人员所接受。此外,回调函数能够提供动态(运行时的)扩展性,而虚成员则只能提供静态(编译时的)扩展性。
回调调函数的主要缺点在于,与虚成员相比显得太过重量级。通过委托进行调用的性能不如调用虚成员。另外,由于委托是对象,因此它们会消耗内存。
ü 考虑实用回调函数来让框架能够执行用户提供的代码。
ü 考虑实用事件来让用户定制框架的行为,这样就不需要用户对面向对象设计有深入的了解。
ü 要优先使用事件,而不是简单的回调函数。
û 避免在对性能要求很高的API中使用回调函数。
ü 要理解在调用委托时可以执行任何代码,这可能会引起安全性、正确性及兼容性的问题。
5.1.4. 虚成员
虚成员可以被覆盖,从而可以改变子类的行为。从提供的扩展性来看,虚成员与回调函数旗鼓相当,但从执行性能和内存消耗来看,虚成员更胜一筹。此外,如果要为已有的类型创建一个特殊的版本(特化),那么使用虚成员会感觉更白然。
与回调函数相比,虚成员的主要缺点在于它的行为只能静态修改(在编译时修改),而回调函数的行为则可以动态修改(在运行时修改)。
虚成员和回调函数一样,设计、测试及维护的开销都非常高,这是因为用户可能会用设计者意料之外的方式来覆盖虚函数,还可能会执行任何代码。
û 不要使用虚成员,除非有合适的理由,同时你对设计、测试及维护虚成员的开销有清楚的认识。
ü 要优先使用受保护的虚成员,而不是公有的虚成员。公有成员应该通过调用受保护的虚成员的方式来提供扩展。
public Control
{
public void SetBounds(…)
{
SetBoundCore(…);
}
protected virtual void SetBoundCore(…)
{…}
}
5.1.5. 抽象(抽象类型与抽象接口)
û 不要提供抽象,除非为该抽象开发出具体实现并用到该抽象的API对其进行过实际测试。
ü 要在设计抽象时,谨慎地选择抽象类或是抽象接口。
ü 考虑为抽象的具体实现提供参考测试。
5.2. 基类
严格地说,当一个类有另一个类派生自它时,这个类就成为了基类。此处我们把基类限定为这样的类:其设计目的不是为了直接使用或提供常用的抽象,而是为了让其他类通过继承它来重用它的默认实现。
基类通常位于继承层次的中部,根位于顶部,而一些自定义实现则位于底部。基类是为了帮助用户实现抽象。例如,框架为有序项集提供的抽象之一是IList<T> 接口,但要实现IList<T>并不容易,因此框架提供了许多基类,比如Collection<T>和KeyedCollection<TKey,TItem>,以帮助用户实现自定义的集合。
public class OrderCollection : Collection<Order>
{
protected override void SetItem(int indext, Order item)
{
if (item == null) throw new ArgumentNullExcaption(...) ;
base.SetItem(index, item) ;
}
}
ü 考虑使基类为抽象类,即使它们并不包含任何抽象成员。
ü 考虑把基类用于主要场景的类型分开,放到单独的命名空间中。
û 避免在命名基类时使用“Base”后缀,如果该类会用于公用API。
5.3. 密封
密封是一种阻止扩展的有效机制。开发人员既可以密封整个类,也可以密封类中的单个成员。密封整个类可以是用户不能继承该类,密封一个成员可以使用户不能覆盖该成员。
public class NonNullCollection<T>:Collection<T>
{
protected sealed override void SetItem(int index, T item)
{…}
}
û 不要把类密封起来,除非有恰当的理由。
把类密封起来的恰当理由包括:
Ø 类为静态类。
Ø 类的保护成员保存了需高度保密的机密信息。
Ø 类继承了许多虚成员,把这些虚成员一个一个密封起来代价太高,还不如把这个类都密封起来。
Ø 类是attribute,需要能在运行的时候快速查找。
密封的attribute在性能上比不密封的attribute略好。
û 不要在密封类中声明保护成员或虚成员。
ü 考虑在覆盖成员时,将其密封。
public class FlowSwitch:SourceSwitch
{
protected sealed override void OnValueChanged()
{…}
}