interface与多重继承的观念
不管是java的interface或是c++的多重继承﹐在物件导向的理论里﹐都算是蛮新颖的概念。所以这里我们谈的﹐是以程式语言的角度﹐看看java interface的所有意义与功能﹐是否c++的多重继承能全部诠释?或是相反地以java的来诠释c++的。
首先让我们来复习一下什么是c++的多重继承。 「继承」通常在物件导向程式语言中﹐扮演着程式码的重复利用的重责大任﹐而c++的多重继承则让某一个子类别可以继承许多分属于不同资料型别的父类别如下:
#include <stdio.h>
class test1 {
public:
virtual void f1() {puts("test1::f1()"; }
virtual void g1() {puts("test1::g1()"; }
};
class test2 {
public:
virtual void f2() { puts("test2::f2()"; }
virtual void g2() { puts("test2::g2()"; }
};
class test3 : public test1, public test2 {
public:
virtual void gg() { puts("test3::gg()"; }
};
void main() {
test3 t3; t3.f1(); t3.f2();
t3.g1(); t3.g2(); t3.gg();
}
// 程式输出:
test1::f1() test2::f2() test1::g1()
test2::g2() test3::gg()
程式1﹑c++的多重继承
根据[rie96]﹐认为正确使用物件导向技术中之「多重继承」观念﹐应该如下面的例子:
假设有一个木造门﹐则:
1. 此木造门是门的一种(a kind of)。
2. 但门不是木造门的一部份(a part of)。
3. 木造门是木制品的一种。
4. 但木制品不是木造门的一部份。
5. 木制品不是门的一种。
6. 门也不是木制品的一种。
所以您可以发现﹐多重继承在使用时﹐必须非常小心﹐而且在许多时候﹐其实我们并不需要多重继承的。
java也提供继承机制﹐但还另外提供一个叫interface的概念。由于java的继承机制只能提供单一继承(就是只能继承一种父类别)﹐所以就以java的interface来代替c++的多重继承。interface就是一种介面﹐规定欲沟通的两物件﹐其通讯该有的规范有哪些。如以java程式语言的角度来看﹐java的interface则表示:一些函数或资料成员﹐为另一些属于不同类别的物件所需共同拥有﹐则将这些函数与资料成员﹐定义在一个interface中﹐然后让所有不同类别的java物件可以共同操作使用之。
所以﹐对于java的继承与interface﹐我们总结如下:
1.java的class只能继承一个父类别(用extends关键字)﹐但可以拥有(或称实作)许多interface(用implements关键字)。
2.java的interface可以继承许多别的interface(也是用extends关键字)﹐但不可以实作任何interface。
因此﹐我们可以利用java的interface来模拟c++的多重继承。如上面的例子可以转化如下:
interface test1 {
public void f1();
public void g1();
}
interface test2 {
public void f2();
public void g2();
}
interface test3 extends test1, test2 {
public void gg();
}
class ctest implements test3 {
public void f1() { system.out.println("test1::f1()"; }
public void g1() { system.out.println("test1::g1()"; }
public void f2() { system.out.println("test2::f2()"; }
public void g2() { system.out.println("test2::g2()"; }
public void gg() { system.out.println("test3::gg()"; }
}
class run {
public void run() {
ctest ct=new ctest(); ct.f1();ct.f2();
ct.g1();ct.g2(); ct.gg();
}}
class main {
public static void main (string args[]) {
run rr=new run();
rr.run();
}}
// 程式输出:
test1::f1() test2::f2() test1::g1()
test2::g2() test3::gg()
程式2﹑利用java的interface完成c++的多重继承功能
然而﹐根据[ait96]的文章显示﹐他认为java的interface比c++的多重继承好学很多﹐也较容易懂﹐但是有其限制。对于java interface的易懂﹐在文章中﹐并没有说明。其主要即为「介面继承」与「实作继承」概念的差异。
「介面继承」就是只继承父类别的函数名称﹐然后子类别一定会实作取代之。所以当我们以父类别的指标「多型」于各子类别时﹐由于子类别一定会实作父类别的多型函数﹐所以每个子类别的实作都不一样﹐此时我们(使用父类别指标的人)并不知道此多型函数到底怎么完成﹐因之称为「黑箱设计」。
「实作继承」就是继承父类别的函数名称﹐子类别在实作时﹐也会用到父类别的函数实作。所以我们(使用父类别指标的人)知道此多型函数怎么完成工作﹐因为大概也跟父类别的函数实作差不多﹐因之称为「白箱设计」。
套用的java的interface上﹐我们发现﹐java的interface就是介面继承﹐因为java interface只能定义函数名称﹐无法定义函数实作﹐所以子类别必须用「implements」关键字来实作之﹐且每个实作同一介面的子类别当然彼此不知道对方如何实作﹐因此为一个黑箱设计。
java的类别继承则为实作继承﹐子类别会用到父类别的实作(更正确地说应该是父类别有定义实作﹐所以子类别可能会使用到﹐即使不使用到也会遵循父类别实作的演算法)﹐所以父类别与子类别有一定程度的相关性﹔不像介面继承﹐彼此只有函数名字刚好一样而已。
介面继承与实作继承﹐应对至java的interface﹑class﹑extends与implements关键字﹐很容易了解其含意。但是c++的继承机制﹐似乎就没有那么容易解释清楚的!所以这就是[ait86]文章中所表示的意思:c++多重机制比较复杂。
所以接下来我们将讨论:
c++的多重继承有什么功能﹐是java的interface所达不到的?
在c++的arm中﹐或是[str94]的多重继承章节里﹐皆提到了下述著名的例子:
#include <stdio.h>
class t1 {
public:
virtual void f() { puts("t1::f()"; }
virtual void g() { puts("t1::g()"; }
};
class t2 : public virtual t1 {
public:
virtual void g() { puts("t2::g()"; }
};
class t3 : public virtual t1 {
public:
virtual void f() { puts("t3::f()"; }
};
class t4 : public t2, public t3, public virtual t1 { …};
void main() {
t4 *tt4=new t4; t2 *tt2=tt4; t3 *tt3=tt4;
tt4->f(); tt4->g(); tt2->f(); tt3->g();
}
// 程式输出:
t3::f() t2::g() t3::f() t2::g()
程式3﹑c++著名的环状继承
由上例﹐我们发现﹐c++的多重继承具有下列两个特质是java的interface所不能达到的功能。
图1﹑c++的环状多重继承
c++的多重继承可以形成环状继承关系﹐如图1。但是不管是java的继承机制或是interface﹐都不容许有环状的情况发生。换句话说﹐因为c++有virtual base的属性的父类别﹐所有在多重继承时﹐允许父类别被继承两次以上。但java则完全不行。
本题中的tt4指标﹐转成tt2指标后﹐执行f()函数时﹐仍然会正确地执行tt4中的f()函数﹐也就是t3::f()。我们可以发现﹐这种找函数的方式﹐是先找函数的正确名称﹐再找函数所属的类别的正确名称。与java的虚拟函数(或称为abstract函数)不同。java的是先找指标(或参考)所属的正确类别名称﹐再继续找类别名称下的正确函数名称。
图2﹑对于虚拟函数c++与java的各别作法
对于第二点参考图2。c++的虚拟函数﹐可以参考[sou94]﹐c++编译器对于每一个虚拟函数﹐均建立一个虚拟函数表与之应对﹐因为每一个虚拟函数在一个继承树可能有许多子类别实作之。因此在实际执行时﹐是先找虚拟函数表﹐然后再寻找与自己类别阶层等级最靠近的那个函数实作。所以我们可以将一个父类别指标转换成子类别后﹐反而仍执行母亲那辈的虚拟函数。
而java则不能透过interface执行上述功能。java的抽象函数(abstract function)事实上就是c++的纯虚拟函数(virtual function()=0)﹐没有像c++可以有非纯虚拟函数(就是子类别不一定要定义的虚拟函数)﹐所以很难执行上面复杂的例子。并且﹐java的interface里的函数预设为抽象函数﹐也就是如果某类别实作此interface的话﹐interface里的所有函数都必须全部实作。因此在实际执行时﹐先决定interface物件转型成某类别的物件﹐于是再执行该类别内的函数实作。
乍看之下﹐似乎c++的多重继承功能较完整﹐那么:
java的interface概念﹐是否可用c++的多重继承模拟出来呢?
目前﹐在微软的ole技术中﹐确实是用c++的多重继承模拟ole中的interface概念﹐可以参考mfc中关于ole com的写作。如下面的程式:
class cosomeobject : public iunknown, public ipersiet {
// iunknown methods
virtual dword addref(void);
virtual dword release(void);
virtual hresult queryinterface(refild, lpvoid far*);
//ipersist methods
virtual hresult getclassid(lpclsid pclsid);
};
(注:参见mfc internals p.p 442)
然而﹐考虑以下的问题:java interface的特色为﹐interface间可以继承。但这里所谓的继承﹐事实上只是子interface包括全部的父interface内的成员﹐无法像java的类别或是c++的类别那样﹐可以用子类别的新定义将父类别取代之。这也就是所谓的「介面继承」与「实作继承」的差别。换言之﹐java interface继承(注意﹐不是子类别implements父interface)只是一种「包含关系」而已﹐甚至包含不可以重复。所以﹐interface概念用在软体模组间的介面定义便非常厉害﹐如corba的idl便是。因此﹐java interface仍然拥有许多优点﹐但如果我们要以c++的多重继承模拟(或称之为藉此学习java interface概念)时﹐在c++这边该如何映对呢?
我们以程式2的java程式为主﹐观察c++的模拟interface版﹐该如何映对。
#include <stdio.h>
class test1 {
public:
virtual void f1()=0;
virtual void g1()=0;
};
class test2 {
public:
virtual void f2()=0;
virtual void g2()=0;
};
class test3 : public test1, public test2 {
public:
virtual void gg()=0;
};
class ctest : public test3 {
public:
void f1() { puts("test1::f1()"; }
void g1() { puts("test1::g1()"; }
void f2() { puts("test2::f2()"; }
void g2() { puts("test2::g2()"; }
void gg() { puts("test3::gg()"; }
};
void main() {
ctest ct; ct.f1();ct.f2();
ct.g1();ct.g2(); ct.gg();
}
// 程式输出:
test1::f1() test2::f2() test1::g1()
test2::g2() test3::gg()
程式4﹑c++对应的java interface概念
由程式4可知﹐其实只要在每个类别中的每一个函数都宣告成纯虚拟函数﹐则c++类别就变成java interface概念了。
因此我们下一结论:c++的多重继承功能较广﹐java的interface功能只是其中的一个子集。因为c++的虚拟函数可以有纯虚拟函数﹐也可有非纯虚拟函数﹐而java只有抽象函数﹐所以功能模式少一种﹐自然能达到的效果较少一些。
但这并不代表java的interface就比较差﹐因为interface的观念较简单﹐全部动态的抽象函数也正代表着java为一纯物件导向语言。与c++不同的是﹐c++考虑许多执行效率的问题﹐所以语言本身就变的较复杂化﹐同时c++的编译器也是公认难写的﹐多重继承更是一大挑战。java的interface虽然好象少了一些功能﹐但其实那些功能在一般的程式设计中也很少碰到﹔并且观念简洁的java interface﹐在设计软体程式时更可以避免许多陷阱。总之﹐两种程式语言机制是很难比较谁好谁坏的。
顺带一提的是:最近有一股新趋势﹐就是物件导向程式语言所引导出来的新的概念﹐反过来引响物件导向分析与设计的基础理论。如java的interface或是微软的ole interface﹐使得booch﹑omt整合方法­­uml﹐对软体模组间的介面有了新的定义﹔或是c++的stl与template﹐促进许多学者对静态的物件关系又有了新解等。■
