首页 新闻 论坛 群组 Blog 文档 下载 读书 Tag 网摘 搜索 开源 FAQ 第二书店 博文视点 程序员
频道: 研发 数据库 中间件 信息化 视频 .NET Java 游戏 移动 服务: 人才 外包 培训
    图书品种:235680
       
热门搜索: ASP.NET Ajax Spring Hibernate Java

条款20:明辨接口实现和虚方法重写

乍一看,实现接口和重写虚方法好像一样。我们都是在一个类型中为另一个类型中声明的成员提供定义。这种初始印象非常具有欺骗性。实际上,实现接口和重写虚方法之间的差别很大。接口中声明的成员并非虚方法——至少,默认情况下不是虚方法。

派生类不能重写基类中实现的接口成员。接口可以被显式实现(explicitly implemented),这会使它们被排除在一个类的公有成员之外。接口成员与虚方法的概念不同,用法也不同。

但是我们也可以用一种让派生类可以重写实现的方式来实现接口。这时候,只要为派生类创建一些挂钩(hook)就可以了。

为了演示二者之间的差别,我们先来看一个简单的接口和一个类对它的实现。

interface IMsg

{

  void Message();

}

public class MyClass : IMsg

{

  public void Message()

  {

    Console.WriteLine( "MyClass" );

  }

}

Message()方法是MyClass公有接口的一部分。Message()也可以通过IMsg来访问,因为IMsg是MyClass类型的一部分。现在如果添加一个派生类,情况就会变得稍微有些复杂:

public class MyDerivedClass : MyClass

{

  public new void Message()

  {

    Console.WriteLine( "MyDerivedClass" );

  }

}

注意,上面的Message()方法的定义中必须添加new关键字(参见条款29)。MyClass.Message()并不是一个虚方法。派生类不能为其提供一个重写的版本。MyDerived类实际上创建了一个新的Message()方法。

这个新的Message()方法和MyClass.Message()方法并不构成重写关系,它仅仅是把MyClass中的Message()方法给隐藏了。另外,MyClass.Message()仍然可以通过IMsg引用来访问:

MyDerivedClass d = new MyDerivedClass( );

d.Message( ); // 打印"MyDerivedClass"。

IMsg m = d as IMsg;

m.Message( ); // 打印"MyClass"。

接口方法并不是虚方法。当我们实现一个接口时,我们是在类型中声明一个特定接口合同的具体实现。

但是,我们通常要创建接口,然后在基类中实现它们,并在派生类中更改它们的实现。是的,我们可以这么做,有两种做法供我们选择。如果不能访问基类,我们可以在派生类中重新实现接口:

public class MyDerivedClass : MyClass, IMsg

{

  public new void Message()

  {

    Console.WriteLine( "MyDerivedClass" );

  }

}

添加IMsg接口会改变派生类的行为,所以现在IMsg.Message()会使用派生类中提供的实现:

MyDerivedClass d = new MyDerivedClass( );

d.Message( ); // 打印"MyDerivedClass"。

IMsg m = d as IMsg;

m.Message( ); // 打印"MyDerivedClass"

在MyDerivedClass.Message()方法上,我们仍然需要使用new关键字。这可能会让大家感觉存在某种问题(参见条款29)。基类中对IMsg.Message()的实现仍然可以通过一个基类对象的引用来获得:

MyDerivedClass d = new MyDerivedClass( );

d.Message( ); // 打印"MyDerivedClass"。

IMsg m = d as IMsg;

m.Message( ); // 打印"MyDerivedClass"

MyClass b = d;

b.Message( ); // 打印"MyClass"

修正这个问题的唯一办法就是修改基类——将接口方法声明为虚方法:

public class MyClass : IMsg

{

  public virtual void Message()

  {

    Console.WriteLine( "MyClass" );

  }

}

public class MyDerivedClass : MyClass

{

  public override void Message()

  {

    Console.WriteLine( "MyDerivedClass" );

  }

}

现在MyDerivedClass——以及所有继承自MyClass的类——都可以声明它们自己的Message()方法。每次被调用的也都是重写的版本,不管是通过MyDerivedClass引用,还是通过IMsg引用,或者通过MyClass引用。

如果不喜欢这种不够纯粹的虚函数,可以对MyClass的定义做一个小的改动:

public abstract class MyClass, IMsg

{

  public abstract void Message();

}

是的,我们可以在实现一个接口的同时,不实现接口中的方法。通过将接口中的方法声明为抽象方法,我们实际上是在要求所有派生类都必须提供该接口的实现。IMsg接口是MyClass声明的一部分,但是定义该方法的工作被延迟到了各个派生类中。

显式接口实现(explicit interface implementation)使我们可以在实现一个接口的同时,将其成员从类型的公有接口中隐藏掉。它为实现接口和重写虚方法之间的关系带来了一些其他的变数。当有一个更合适的版本可用时,我们可以使用显式接口实现来限制客户代码直接调用接口方法。条款26中的IComparable设计惯用法向我们详细地展示了这一点。

实现接口拥有的选择要比创建和重写虚函数多。我们可以为类层次创建密封(sealed)的实现、虚实现或者抽象的合同。我们也可以决定派生类如何以及何时修改“基类中实现的接口成员的默认行为”。接口方法不是虚方法,而是一个单独的合同。

查看所有评论(0)条】

最近评论



正在载入评论列表...
热点评论