接口映射的实现及原理

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


可以看出,每一个“方法()”,实际上都是不同的。
在“有接口的继承”中,接口将“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 本园第一神棍 阅读(1671) 评论(6)  编辑 收藏 网摘 所属分类: 设计模式

  回复  引用  查看    
#1楼 2004-11-18 22:11 | 吕震宇      
好文章!感觉你在面向对象编程上面很有见解!学习!
  回复  引用    
#2楼 2004-11-19 08:36 | Ninputer [未注册用户]
Visual Basic .NET是不允许重新实现接口的,你的代码会产生编译错误!
Paul Vick已经明确指出这是人为限制的,但即使Visual Basic 2005已经支持了,也会给出无法关闭的警告。这说明这种用法绝对是不好的。
  回复  引用    
#3楼 2004-11-19 08:39 | Ninputer [未注册用户]
Private Sub Test()Sub Test.接口.方法() Implements 接口.方法
这种语法是不被支持的,方法名称是不能带.的。所以VB没法实现C#对接口的显式实现。同时C#也不支持VB的换名实现和Protected实现等等
  回复  引用    
#4楼 2004-11-19 08:53 | Ninputer [未注册用户]
我知道了,你在看Reflector翻译的代码……(这个可不是学习语言的工具)
你的解释让我想到真实情况可能是这样:
1 .NET每了类都有一个属于自己的默认接口(这里接口指类与外界交互的公共属性+方法+事件,为了区分,将用interface定义的东西称为接口类型)
2 从父类继承将同时继承父类的代码和默认接口
3 类实现接口类型不能继承接口类型自己的接口,而只是提供相应的实现代码。这些实现代码的真实容器属于类本身而不是继承自接口
4 尽管如此,实现接口在CLR内部仍按照继承一样的原理进行。
  回复  引用    
#5楼 2004-11-19 09:14 | Ninputer [未注册用户]
等等,仍然不对。用C++/CLI就可以轻易破除这个限制,让类继承也实现换名重写。
真实的情况是:
任何重写和实现接口的操作,都只是一个关系映射。任何子类重写/实现父类和接口的方法都只是对父类/接口中相应的方法做了一个标记,以便从父类/接口的引用访问。而重写所用的函数本身,只是属于子类,不是继承自父类或接口,其名称和访问级别必须与父类相同只是C#人为做出的限制!VB在接口方面首先破除了限制,而C++/CLI则更加激进在一切重写操作上破除了限制。
现在就可以说了Interface就是类,只是限制所有成员为抽象,不可包含任何代码,且必须公开,而限制终归是限制,接口和类相同部分的实现原理是彻底一样的。
  回复  引用  查看    
#6楼 [楼主]2004-11-19 20:08 | 寒枫天伤      
Niputer,语法的限制是编译器作出的。
之前我并未测试过VB.NET的接口映射,十分抱歉,刚才试了一下,才发现,无法通过编译。

这些限制都是编译器进行的限制,是对语言本身的一种支持,之前没有进行详细的验证,是我的失误,实在不好意思。

“Interface就是类”,这一点肯定是的,因为早期本身就有一种“接口模式”,用于进行接口的行为,因为使用过于频繁,所以现代的语言直接引入这一元素,作为基础的语言中提供的工具。

感谢你的回复,纠正了一个我的错误




标题  
姓名  
主页
Email (博主才能看到) 
验证码 *  看不清,换一张 [登录][注册]
内容(请不要发表任何与政治相关的内容)  
  登录  使用高级评论  新用户注册  返回页首  恢复上次提交      
该文被作者在 2004-11-19 00:19 编辑过
Google站内搜索
[推荐职位]上海盛大网络招聘架构师

China-pub 计算机图书网上专卖店!6.5万品种 2-8折!
近千种 9-95 新二手计算图书火热销售中!
开发者征途系统新作:《设计模式——基于C#的工程化实现及扩展》

相关文章:

相关链接:
 
Web Counter