接口映射的实现及原理

我们先看看 该代码的类图的层次结构吧


可以看出,每一个“方法()”,实际上都是不同的。
在“有接口的继承”中,接口将“Test.接口.方法()”进行了重新映射。这个映射是如何完成的呢?

看看如下的IL代码:
在采取使用它们自己的类作为访问入口时,代码实现如下:


没有接口的继承,它实现的方式如下:

.method public hidebysig instance void 方法() cil managed
{
      // Code Size: 11 byte(s)
      .maxstack 1
      L_0000: ldstr "\u6211\u53ea\u80fd\u591f\u4f7f\u7528 
new \u6765\u5c4f\u853d\u5b83\uff0c\u4e0d\u8fc7\uff0c\u5728IDesign\u7684
\u7f16\u7a0b\u89c4\u8303\u4e2d\uff0c\u4e0d\u63a8\u8350\u7528 new\u3002"
L_0005: call void [mscorlib]System.Console::WriteLine(string) L_000a: ret }


而有接口的继承,实现方式是这样的:
.method private hidebysig newslot virtual final instance void Test.接口.方法() cil managed
{
      .override Test.接口::方法
      // Code Size: 11 byte(s)
      .maxstack 1
      L_0000: ldstr "\u8fd9\u5c31\u53eb\u505a\u63a5\u53e3\u7684\u91cd\u6620
\u5c04\uff0c\u8fd9\u53ea\u662f\u6280\u5de7\u6027\u7684\u4e1c\u897f\u800c\u5df2\u3002"
L_0005: call void [mscorlib]System.Console::WriteLine(string) L_000a: ret }


在这里可以看出,有接口的继承实际上对“方法”进行override,但这个override是覆盖的接口的方法的实现成员,并非是类的方法成员。

然后,我们继续看看在客户类中对两者进行调用的IL

没有接口继承的IL
      L_001d: newobj instance void Test.没有接口的继承::.ctor() //创建实例
      L_0022: stloc.2
      L_0023: ldloc.2 
      L_0024: callvirt instance void Test.没有接口的继承::方法()
有接口继承的IL
      L_003a: newobj instance void Test.有接口的继承::.ctor() //创建实例
      L_003f: stloc.s 有接口的继承1
      L_0041: ldloc.s 有接口的继承1
      L_0043: callvirt instance void Test.基类::方法()

如果是采取接口访问时,则状况如下:

没有接口继承的IL
      L_0029: newobj instance void Test.没有接口的继承::.ctor()
      L_002e: stloc.3
      L_002f: ldloc.3  
      L_0030: callvirt instance void Test.接口::方法()

有接口继承的IL
      L_0048: newobj instance void Test.有接口的继承::.ctor()
      L_004d: stloc.s 接口3
      L_004f: ldloc.s 接口3
      L_0051: callvirt instance void Test.接口::方法()
----------------------------------------------------------------------------
       注意一下上面标记颜色的部分,可以看出,采用不同的访问方式,实现的结果并不尽相同。也就是说,针对接口实现了的方法,与类本身自带的实现,是两回事,这种情况很类似于采用new关键字进行创建一个新的同名成员方法时遭遇的问题。
      结果之所以会不同,是由于访问的方式不同的原因,如果采用“基类”来访问,很明显,这里获得的就是基类的成员实现。也就是说,这也是多态的一种体现,但并非不可预知或不可控制的。

  看见有人说VB很难实现接口的映射,实际上并不是这样,要解释一下这个问题,这里不得不说一下,强类型的C#语法的含义。
      “基类 objBase = new 基类();” 这种语法,表示的是使用“基类”来访问新建的“基类”的实例,换而言之,“基类 objDevired = new 没有接口的继承();”表示的就是使用基类来访问新建的“没有接口的继承”的实例。
  接口的访问,也是如此,在原理上,将接口看作是一个十分特殊的抽象类,它与一般的抽象类的区别在于强制了成员的实现(接口的语法由编译器来验证的,在CLR并未提供限制性机制),所以,在有一些设计模式中,也可以看到采用了接口-抽象类-具体类的方式来绕开这类强制成员实现的检查,从而提高灵活性。
      所以,VB.NET照样也可以完成接口映射,因为接口本身的实现机制仍旧是依赖于类的基本特征的。

      示例伪代码如下:     

Friend Interface 接口
      
' Methods
      Sub 方法()
End Interface


Friend Class 基类
      
Implements 接口

      
Public Sub New()
      
Private Sub Test.接口.方法() Implements 接口.方法
      
Public Sub 方法()
End Class


Friend Class 没有接口的继承
      
Inherits 基类

      
Public Sub New()
      
Public Sub 方法()
End Class


 
Friend Class 有接口的继承
      
Inherits 基类
      
Implements 接口

      
Public Sub New()
      
Private Sub Test.接口.方法() Implements 接口.方法
End Class



  但就接口本身来说,是一个便利性的工具,在编程时的认识上,我们不应该将它与类视作“同一类东西”,它是对类的之间关系的一个描述。对于类本身的机制来说,继承与多态都是提供父子关系的纵向关系描述,而横向的关系描述,则反映得并不是很好,接口通过一定的实现机制,则部分性地弥补了这个空缺。
  像代码中所描述那样,接口仅是作为一个便利性的工具存在,在面向接口的编程中,提供一切都围绕接口而进行,所以,在此类的编程模式下,可以采用接口为替换一个方法实现提供十分理想的方式,虽然这比起滥用继承并没有什么优势,但它是一个思想与观念上的转变,从本质上来说,接口映射与继承中的new一样,作为技巧用用可以,但它本身并不是一个值得推荐的方式。

posted @ 2004-11-18 19:52  一根神棍研古今  阅读(4163)  评论(7编辑  收藏  举报
Web Counter