Effective C# Item19:定义并实现接口优于继承类型

    这个话题不仅仅是针对.NET的,在其他面向对象语言的环境中,例如Java,都会有接口和抽象类,对于究竟是选择接口还是抽象类,已经有了太多的讨论,包括设计模式中都有了一条设计原则:组合优于继承,虽然这条原则和我们要讨论话题没有太大联系,但是可以看出在这方面如何做出选择,并没有一个万能的解决方案,一般都要见招拆招,具体问题具体分析。

   抽象类一般是作为一个类层次结构的顶端,它一般有两个用途:1)定义逻辑骨架;2)抽取共通方法。它为一组相关的类型提供了一个公用的抽象;而接口是一种按合约进行设计的方式,一个实现了接口的类型,必须要实现接口中定义的方法。

   继承一种“is a”的关系,而接口实现则是一种“behave as”的关系,抽象类描述了对象是什么,而接口描述了对象的行为方式。

    在接口中,不能包含实现,也不能包含任何具体的数据成员,接口是在声明一种合约,所有实现接口的类型都要负责履行其中的约定。

    抽象类可以为派生类型提供一些具体的实现,我们可以指定数据成员、具体的方法、虚方法的实现、属性、事件以及索引器。它还可以实现一些具体的方法,因此可以为子类提供一些共通的可重用代码。抽象类可以为任何具体的行为提供一个实现,在这方面,接口是不可以的。

    在抽象基类和接口之间做选择,实际上是一个如何随着时间的推移而更好的支持抽象的问题。接口的特点是比较稳定:我们将一组功能封装在接口中,作为其他类型的实现合约;而基类则可以随着时间的推移进行扩展,这些扩展会成为每一个子类的一部分。

    C#中,我们可以混合使用抽象类和接口,可以继承一个类,然后实现多个接口。

    面向对象中的一个设计原则是面向接口编程,因此无论对于接口,还是抽象类,在传递参数的过程中,使用抽象类型或者接口类型,会比直接使用派生类型或者实现接口的类型要好,在扩展的空间。

    当我们在传递参数的过程中,使用类作为传递方式,那么我们实际上是将整个类的接口暴露给外界,通过使用接口,我们可以选择只提供哪些期望给用户的方法和属性,用来实现接口的类属于实现细节,它会随着时间的推移而发生改变。(关于这一点,我个人感觉主要是由于.NET中单继承、多接口的机制造成的,如果是单继承、单接口的机制,那么即使是在传值过程中使用接口,也会将接口的方法暴露给外界)。

   我们来看下面的代码。

代码
1 public interface InterfaceA
2 {
3 bool MethodA();
4 }
5
6 public interface InterfaceB
7 {
8 bool MethodB();
9 }
10
11 public abstract class BaseClass
12 {
13 public abstract void AbstractMethodA();
14 }
15
16 public class DerivedClass : BaseClass, InterfaceA, InterfaceB
17 {
18 public override void AbstractMethodA()
19 {
20 throw new NotImplementedException();
21 }
22
23 #region InterfaceA Members
24
25 public bool MethodA()
26 {
27 throw new NotImplementedException();
28 }
29
30 #endregion
31
32 #region InterfaceB Members
33
34 public bool MethodB()
35 {
36 throw new NotImplementedException();
37 }
38
39 #endregion
40 }
   上述代码定义了两个接口、一个抽象类和一个继承自抽象类实现了两个接口的具体类。关于传值过程中使用类和使用接口的区别,我们可以看以下的代码。

代码
1 private static void TestWithInterface(InterfaceA interfaceA)
2 {
3 interfaceA.MethodA();
4 }
5
6 private static void TestWithAbstractClass(BaseClass baseClass)
7 {
8 baseClass.AbstractMethodA();
9 }
10
11 private static void TestWithClass(DerivedClass derivedClass)
12 {
13 derivedClass.MethodA();
14 derivedClass.MethodB();
15 derivedClass.AbstractMethodA();
16 }
    我们定义了三个用于测试的方法,这三个方法在传值时,分别使用接口、抽象类和具体类,我们可以看到使用接口和抽象类传递参数,都是只能访问接口或者抽象类中定义好的方法,而使用具体类传递参数,它既可以访问接口中的方法,也可以访问抽象类中的方法。

   因此,如果我们需要向调用方只公开一部分功能,那么我们应该使用抽象类或者接口的方式进行传值。

 

   总结:基类描述并实现了一组相关类型间共通的行为,接口则描述了一组比较紧密关联的功能, 供其他不相关的具体类型来实现。这两者都有自己的适用范围,类定义了我们要创建的类型,接口则以功能分组的形式描述了那些类型的行为。如果理解好而这之间的差别,我们便可以创建更富表现力、更能应对变化的设计。应该使用类层次来定义相关的类型,然后让它们实现不同的接口,以便通过接口向外界提供功能。

posted @ 2010-01-15 23:17  李潘  阅读(711)  评论(0编辑  收藏  举报