实例看多态 tanrui(原作)
关键字:多态性 继承
近来看了一下多态性,把我的一些感受以例子的形式记录一下。
一.形象理解
两条理解的原则:
- (1)一个派生类对象可以被声明成一个基类,或者是一个基类指针可以指向一个派生类对象:
//c++ code
BaseClass *p;
DerivedClass obj;
p = &obj;
//C# code
BaseClass obj = new DerivedClass();
- (2)把一个对象看做是一个独立的个体,调用对象的
public
成员函数实际上是给这个对象发送一个消息,采取什么样的动作完全由对象自己决定。
Shape
是基类,Circle
和Line
是从Shape
继承出来的,Shape
有draw()
方法,Circle
与Line
分别自己定义了自己的draw()
方法,在下面的代码里:
// Java Code
static void func(Shape s){
s.Draw();
}
如果发生了这样的调用:
Line l = new Line();
Circle c = new Circle();
func(l);
func(c);
一个Circle
和一个Line
被当做Shape
传到函数里去了,然后调用Draw()
,会发生什么情况?因为对象是独立的个体,在func()
里,这两个对象被分别传递了Draw()
消息,叫它们绘制自己吧,于是他们分别调用了自己类里定义的Draw()
动作。
通过这两条原则我们可以理解上面的多态。正是由于多态,使得我们不必要这样去做:
IF 你是一个Circle THEN 调用Circle的Draw()
ELSE IF 你是一个Line THEN 调用Line的Draw()
ELSE …
我们只要给这个被声明成为Shape
的对象发送Draw
消息,怎么样去Draw
就由对象自己去决定了。
二.一切皆因虚函数
先看看实现多态的基本条件:
- (1)基类含有虚函数
- (2)继承类把这个虚函数重新实现了
- (3)继承类也可能没有重新实现基类的所有虚函数,因此对于这些没有被重新实现的虚函数不能发生多态。
再看一下几种语言里一些特别的规定:
C++:
(1)虚函数用
virtual
关键字声明。
(2)virtual void Func(para_list) = 0;
这样的虚函数叫做纯虚函数,表示这个函数没有具体实现。包含纯虚函数的类叫做抽象类,如果他的继承类没有对这个纯虚函数具体用代码实现,则这个继承类也是抽象类。抽象类不能被实例话(就是说不能创建出对象)。
(3)继承类重新实现基类的虚函数时,不需要做任何特别的声明。
(4)如果不用virtual
关键字修饰,并且在派生类里重新实现了这个方法,这仅仅是一个简单的覆盖,不会发生多态,我们暂称它非多态吧。Java:
(1)Java没有virtual关键字,Java把一切类的方法都认为是虚函数。
(2)继承类重新实现基类的虚函数时,不需要做任何特别的声明。因此在Java里只要重新实现了基类的方法,并且把继承类对象声明为基类,多态就要发生。因此Java对多态的条件相对是比较低的。//Java Code class BaseClass { public void hello(){}; } class DerivedClass extends BaseClass { public void hello() { System.out.println("Hello world!"); } public static void main(String args[]) { BaseClass obj = new DerivedClass(); obj.hello(); } }
输入是Hello world!。这样就实现了多态。
(3)虚函数用abstract声明,含有虚函数的类是抽象类,也要用abstract关键字修饰。
//Java Code public abstract AbstractClass { public abstract void hello(); //… }
C#:
C#对于多态的编写是最为严格和严谨的。
(1)虚函数用virtual
声明。
(2)纯虚函数用abstract
声明,含纯虚函数的类是抽象类,必须用abstract
关键字修饰。
(3)如果仅仅是覆盖基类的非虚方法,则需要用new
关键字声明:
//C# Code
public class BaseClass
{
public void hello()
{
System.Console.WriteLine("Hello,this come from BaseClass");
}
}
public class DerivedClass : BaseClass
{
public new void hello()
{
System.Console.WriteLine("Hello,this is come from DerivedClass");
}
public static void Main()
{
BaseClass obj = new DerivedClass();
obj.hello();
}
}
输出为Hello,this come from BaseClass,
也就是说这并没有实现多态(非多态)。
(4)通过virtual – override
、abstract – override
组合实现多态。
当派生类重新实现基类的虚函数(或纯虚函数)时,必须用override
关键字进行修饰。
//C# Code
public abstract class AbsBaseClass
{
public abstract void hello();
}
public class DerivedClass : AbsBaseClass
{
public void hello()
{
System.Console.WriteLine("Hello world!");
}
public static void SayHello(AbsBaseClass obj)
{
obj.hello();
}
public static void Main()
{
DerivedClass _obj = new DerivedClass();
DerivedClass.SayHello(_obj);
}
}
输出为Hello world!
三.多态的反溯
继承类对象在发生多态时,并是不完全抛开基类不管的,它会去查看基类的虚函数列表,在这个列表的范围内才会发生多态。
让我们来看一个比较复杂的例子:
// Java Code
class A {
protected void hello(Object o) {
System.out.println("A - Object");
}
}
class B extends A {
protected void hello(String s) {
System.out.println("B - String");
}
protected void hello(Object o) {
System.out.println("B - Object");
}
};
class C {
public static void main(String args[]) {
Object obj = new Object();
String str = "ABC";
A a = new B();
a.hello(obj);
a.hello(str);
}
};
输出结果为:
B – Object
B – Object
正如上面所说的,由于基类里没有参数类型为String
的虚函数,因此B
的hello(String)
方法不参与多态。调用a.hello(str)
时,由于String
是Object
的继承类,因此这个str
被作为一个Object
传入了B
的hello(Object)
,这一点正如我们的原则一所述。
四.接口——仅仅是更抽象的抽象类
接口是类的协定,但由于接口又参与多态性,从这一点说,我们认为它是更为抽象的抽象类,如下:
// Java Code
interface IBase {
void hello();
}
class DerivedClass implements IBase {
public void hello() {
System.out.println("Hello world!");
}
public static void main(String args[]) {
IBase obj = new DerivedClass();
obj.hello();
}
}
在Java与C#中,类只能从一个基类派生出来,但是可以实现多个接口。
这里有一个小小的问题:如果IBase1
与IBase2
里都声明了有hello()
方法,DerivedClass
实现了这两个接口,当然需要具体把hello()
实现出来。
interface IBase1
{
void hello();
}
interface IBase2
{
void hello();
}
public class DerivedClass1 : IBase1,IBase2
{
public void hello()
{
System.Console.WriteLine("Hello world!");
}
}
public class DerivedClass2 : IBase1,IBase2
{
void IBase1.hello()
{
System.Console.WriteLine("This come from IBase1");
}
void IBase2.hello()
{
System.Console.WriteLine("This come from IBase2");
}
public static void Main()
{
IBase1 obj_1 = new DerivedClass1();
IBase2 obj_2 = new DerivedClass1();
IBase1 obj_3 = new DerivedClass2();
IBase2 obj_4 = new DerivedClass2();
obj_1.hello();
obj_2.hello();
obj_3.hello();
obj_4.hello();
}
}
输出为:
Hello world!
Hello world!;
This come from IBase1
This come from IBase2
有两点注意:
(1)DerivedClass2
的实现方法叫显式实现,这种方法C#才支持,在Java里不能实现。
(2)进一步测试表明:hello()
方法并不属于DerivedClass2
:
加入这样的代码
DerivedClass2 t = new DerivedClass2();
t.hello();
编译错误:test.cs(44,3): error CS0117: “DerivedClass2”并不包含对“hello”的定义
那就是说这个方法是属于接口的,但是接口不能含有具体的实现代码,这里是不是存在一定的矛盾呢?
欢迎与我交流:tanrui@sjtu.edu.cn
对该文的评论 人气:472
wr960204 (2004-3-11 17:33:43)
有些观点我不能同意。但也不能说我认为的就是对的