实例看多态

** 实例看多态 ** ** **

近来看了一下多态性,把我的一些感受以例子的形式记录一下。

一.形象理解
两条理解的原则:
( 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) 继承类也可能没有重新实现基类的所有虚函数,因此对于这些没有被重新实现的虚函数不能发生多态。

再看一下几种语言里一些特别的规定:

1. C++ :
( 1 )虚函数用 virtual 关键字声明。
( 2 ) virtual void Func(para_list) = 0; 这样的虚函数叫做纯虚函数,表示这个函数没有具体实现。包含纯虚函数的类叫做抽象类,如果他的继承类没有对这个纯虚函数具体用代码实现,则这个继承类也是抽象类。抽象类不能被实例话(就是说不能创建出对象)。
( 3 )继承类重新实现基类的虚函数时,不需要做任何特别的声明。
( 4 )如果不用 virtual 关键字修饰,并且在派生类里重新实现了这个方法,这仅仅是一个简单的覆盖,不会发生多态,我们暂称它非多态吧。

2. 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();
//…
}

3. 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 IBase 1” );
}

void IBase2.hello()
{
System.Console.WriteLine(“This come from IBase 2” );
}

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 ”的定义

那就是说这个方法是属于接口的,但是接口不能含有具体的实现代码,这里是不是存在一定的矛盾呢?

欢迎与我交流: [email protected]

Published At
Categories with Web编程
Tagged with
comments powered by Disqus