隐式接口实现(ImplicitInterfaceImplementation)
译自:Martin Fowler's Bliki 2006.01.04
EN:ImplicitInterfaceImplementation
Java和C#拥有同样的模型:单纯接口类型(pure interface types)[注1] . 我们可以声明一个Pure Interface使用这样的代码: Interface Mailable, 接着我们也可以定义他的一个实现:class Customer implements Mailable (in Java). 一个类可以实现多个Pure Interface.
这一模型存在一个问题,他忽略了这样一个事实: 一旦我们拥有了一个Class, 那么我们便拥有了一个与之相应的隐式接口(Implicit Interface). 以上述的Customer类为例, 其隐式接口是指在Customer中声明的所有Public方法所组成的接口(这种隐式接口的概念在我所见过的OO语言中,都存在).使用Java也好, C#也好, 实现一个隐式接口是办不到的, 也就是说我们不能写出形如下面这样的代码:"Class ValuedCustomer implements Customer".
实现一个隐式接口是什么意思呢?本质上, 他将告诉类型系统, 类ValuedCustomer实现了Customer类中声明的所有public方法,并忽略了他的实现. Customer类的实现包括, 其public方法的实现, 非public方法,以及数据. 也就是说我们拥有了接口继承, 而非实现继承.
拥有隐式接口的这样一个Customer类, 就如同我们拥有了两个东西, 其一是"Customer接口", 该接口里面含有Customer类的所有Public方法, 其二是一个"CustomerImpl"类, 他实现了"Customer接口".
隐式接口这一特性会给我们带了什么便利?
答案之一是在Java的初期, 因为还没有现在这样的Collections框架, 所以我经常想将Vector替换成我自己的实现。但是很遗憾,我们办不到,因为Vector是一个类,我们只能子类化他。我曾一次次的遭遇这样的场面,只是因为我使用的库并没有给我提供相应的接口,以允许我切换实现。是的,没有隐式接口实现,我们没发做到这些。
另一个特别的原因是测试问题, 因为没有接口, stub的时候常常很难, 甚至不可能。这种情况下,我们经常只是为了将来能够进行替换实现,而去定义了一个接口。这种InterfaceImplementationPair方法,能解决这个问题。但是我更喜欢隐式接口实现,因为他更加清晰易读。
但是,为什么编程语言没有允许这一特性呢?我不知道,因为我不是一个语言设计者。但有一次,我有机会与Anders Heljsberg[注2] 讨论这一问题,得到的答案是他更偏好于:“只有你显示的声明了一个成员为Virtual,你才可以对他进行覆写(overriding)”[注3] 。这实质上是担心子类(或者实现者,更确切的说)会破坏其父类,这涉及到如何使用继承,他只适合晚餐后来讨论,这里我不打算详细说明。
这一问题在动态语言中不存在,其他的类如果想实现一个对象的接口时,只需要实现与该对象相同的方法即可。在Java中的动态代理也是解决该问题的一个方法,尽管我认为隐式接口实现要更清晰可读。
Mike Rettig指出了隐式接口实现的一个问题:并非所有的用户都只使用类的Public方法。试想PaymentPlan与Customer类在同一个包中,那么他可以调用Customer类中具有包可见性的方法。那么如果使用Customer的一个隐式接口实现的类,去替换Customer类,PaymentPlan便会出错(译注:因为拿去替换的类已经不再拥有Customer类中具有包可见性的方法)。
Mike Rettig指出的问题是:一个类有多个隐式接口,同时每一个隐式接口又有不同的访问权限控制(译注:包可见性方法与public可见性方法便不完全在同一隐式接口中)。同样的一个类,对其他某些实例,会允许范围更广的访问。
注1:为了与ImplictInterface想对应,我本想把这里翻译成显示接口,但为了与原为对应,这里翻译成了单纯,日语中被翻译成了纯粹。
注2: Anders Heljsberg : turbo pascal, delphi, C#等的作者
注3: 这句话我的理解是,1.隐式的接口继承确实会舍弃一些父类一些东西(属性,非public方法,public方法的实现),这当然是对父类的一种破坏。2. 对于"只有显示声明为Virtual,才能进行覆写"这句话,我理解他也是对“子类不破坏父类”的一种保护,因为如果如果父类的作者不显示声明为Virtual,那么你便不可以对该方法覆写,进而你也无法展开多态。此时程序的设计者拥有更多的控制权(责任)。