优雅OOP(3) - 多态

2009.10.23


三大基石之三 多态

        多态,Polymorphism,意思是多种形态。多态是这里介绍的面向对象编程的三大特色中的最后一个,也将OOP的强大表现的更加淋漓尽致。但也更难于掌握和理解。

       广义上的多态包括以下几个例子:

       a.家里的保险丝坏了,你用铁丝代替,这就实行了强制类型转换,强制把铁丝当做保险丝使用。这叫做强制多态。在代码中的体现是强制类型转换,就像用铁丝去代替保险丝可能产生危险,强制类型转换也可能造成精度丢失等后果。

      b.另一种多态叫重载多态,运算符重载和函数(方法)重载就是重载多态。

      c. 还有参数多态,代码中的表现是类模板和函数模板。实际上,豆浆机,我们可以看成它可以接受多种类型的参数,大豆,花生,甚至是苹果,梨。你放进去什么,他就能弄出什么汁。是不是很像函数模板呢?用了函数模板的函数也是可以接受多种类型的参数,对其进行同样的处理。

       d.下面,重点介绍下包含多态。这是多态中最重要也是应用很多的形式,很多人口中的多态也就是指包含多态。在上次的“继承”中提到了多态,继承也几乎是为了实现多态而准备的。包含多态是指一组类(class)中定义于不同类中的同名成员函数的多态行为。实际上可以理解为通过继承,一个类可以当做多种类使用(可以当做它本身使用,可以当做它的父类的类型使用,也可以当成自己所实现的接口,Interface,类型来使用)。我们还说了汽车是继承与交通工具这个抽象类。下面解释一下抽象类和接口。

       抽象类(Abstract Class)

       人类的世界中存在着许多类似这样的东西,比如交通通工具,药品,餐具。汽车属于交通工具,我们可以说汽车继承于交通工具类,是交通工具的子类。汽车有着交通工具的功能,运送人或物,也具有向上转换的特点,即在用到交通工具时我们都可以考虑汽车。

       但是,交通工具是一个奇特的类,它并不是一个真正可以实例化的类,也就是说交通工具只是一个抽象的概念。你无法去制造出一个交通工具,你只能造出汽车,火车,电车等,但它们都只是交通工具的子类。因为在交通工具类中,我们只定义了一个方法,但却没有写出该方法的实现。我们只说了交通工具可以运输人或货物,却没有明确的指出怎么运送。但汽车类通过轮子在地面上运送货物,火车利用轨道运输,等等。交通工具的子类火车,汽车等指出了如何运输,实现了运输这个方法。

       我们把交通工具这个只是一个抽象概念,不可实例化的类叫做抽象类。想想周围物品所属的类,通讯工具,药品,餐具,茶具等都是抽象类。人们在生活中运用了很多抽象类,为什么呢?抽象类给我们带来了什么?

       应该不难发现,人们将一类东西所共同具有的某种功能整合形成了一个抽象类。交通工具表明,这个抽象类的子类汽车,火车等都具有运送人或货物的功能。药物都具有治疗作用。能够用来喝茶的叫做茶具...

       接口(Interface)

       再说接口,Interface,接口仅仅包含一组方法声明没有具体的代码实现。只要是实现同一个接口的类都具有这个接口的特征。接口如同协议描述了实现接口的对象向外部的承诺。这样其他的对象就可以根据这个协议来和实现接口的对象交流。

       其实,抽象类就实现了接口的功能,如果你暂时不理解接口的话,可以把抽象类理解为接口。

       为什么要用抽象类或是接口?

       至此,你可以再理下思路,想想刚刚的问题。人们为什么要弄出些交通工具,药物,餐具等等这些抽象类?

       因为,抽象类作为一个父类中和了子类的一些共同的行为。重要的是在使用时可以作为子类的共同类型而存在,同时给与了子类最大的灵活性。

       先举一个现实生活中的例子,去买药的时候,你会告诉医生,“我嗓子不舒服,给我开点药。”因为你不知道你该吃什么药,所以就告诉了医生你想要属于药这个抽象类的东西,而不是去买绷带。之后医生会分析你的情况给你写具体的药类。吃完药你的病就差不多了... 再例如,你去问同学借只笔,你要的只是能在纸上写字的东西(笔这种抽象类),并不关心他借给你的是哪种笔(钢笔,圆珠笔,铅笔,他们作为笔的子类实现了笔这种抽象类的写字功能,但实现的方法却不一样)。

       同样,抽象类和接口给程序设计带来了灵活性上的巨大提高。当一个函数的参数类型是一个抽象类时,那么实际上这里可以放任一一个这个抽象类的子类。也就是说我们能更多地考虑某个数据类型能干什么事,而不用关心具体的类是什么。同时抽象类和接口也使我们的代码具有很高的扩展性,使得代码可以“生长”。

       OOP编程中有一条重要的法则,即依赖倒转原则(Dependence Inversion Principle), 其含义是:要做任何具体代码实现,首先要依赖于抽象类的实现。这个原则有多种表述,其中的一种广为人知的解释为:要根据接口编程,而不是根据实现编程。在具体的代码中要尽量使用抽象类,而不使用具体类(这当然只是理想化的境界)。

       下面举一个代码例子,实际上就是《C++面向对象编程基础》里的例子。有一个抽象类Shape,具有draw和erase方法,当然,draw和erase都是空方法,在C++中用纯虚函数实现。Round类继承于Shape类重写了Shape的draw和erase方法用来画圆和清除图形。Line类继承于Shape类重写了Shape的draw和erase方法用来画直线和清除图形。Triangle类继承于Shape类重写了Shape的draw和erase方法用来画三角形和清除图形。它们的UML表述为:

       这样的话我们可以写这样一个drawshape的函数:

 void drawShape( Shape& s ){  s.draw(); }

       注意这个函数的参数类型是Shape ,就意味着这里可以放 Round, Line, 或 Triangle 类的实例,当:

      Round r;
      drawShape(r);

       即可以画出一个圆, 当:

       Line l;
       drawShape(l); 

       画出的就是一条线。现在你大概能体会到抽象类(接口)给代码带来的灵活性了吧。而且我们还可以随时向Shape父类中添加子类。


      至此,面向对象的三大基石都介绍完了,但是不同的面向对象语言还有着它们自己的特色,并且很多东西都还需要在编写代码时练习和体会。

三大基石之一 - 封装
三大基石之二 - 继承与复合
本文地址:http://imyuao.com/entries/blog/posh_oop3

Go Back

Comment