《高效编程十八式》(4/13)形体建模:继承与多态

 

形体建模:继承与多态

王伟冰

    我们经常会用计算机对一些现实的事物进行建模,比如说用代码来描述一些形体。我们可以用边长来描述一个立方体,这样就可以求出它的体积:

    class cube{

    public:

        double length;

        double volume(){

            return length*length*length;

        }

    };

    我们同样可以用半径来描述一个球体:

    class sphere{

    public:

        double radius;

        double volume(){

            return 4*PI*radius*radius*radius/3;

        }

    };

    现在我们有若干个立方体和若干个球体,假设我们要求出它们的总体积,一种方法是分别求立方体和球的总体积,再把这两个总体积加起来。但是这样比较麻烦,我们希望能够统一地对待立方体和球体,所以我们可以为它们建立一个共同的基类object:

    class object{

    public:

        virtual double volume();

    };

    class cube:public object{ //让cube继承object

    ……

    };

    class sphere:public object{ //让sphere继承object

    ……

    };

    然后就可以用object类来统一代表cube和sphere了。比如:

    cube c1;

    object* p=&c1; //将cube类指针转换成object类指针

    double v=p->volume(); //调用cube类的volume()函数

    sphere s1;

    p=&s1; //将sphere类指针转换成object类指针

    v=p->volume(); //调用sphere类的volume()函数

    也就是说,通过基类的指针可以调用不同子类的函数,这种特点称为“多态性”。但是实现多态性有两个前提,一是基类中要给函数加上“virtual”修饰符,这样的函数称为“虚函数”;二是必须通过指针或引用调用虚函数,如果不用指针或引用,比如object p=c1;p.volume();只是简单地调用基类中的volume函数。

    现在我们可以把所有立方体和球的指针一起放到一个object*型的数组里:

    object* objects[10];

    …… //其它操作

    double v=0;

    for(int i=0;i<10;i++)

        v+=objects[i]->volume();

    这样,我们用简单的几句代码求出了所有物体的总体积,而不需要管每个物体究竟是什么形状。因此,对于内部实现不同但对外接口相同的类成员函数,可以建立共同的基类虚函数(或Java和C#中的接口),从而可以在外部代码中统一处理,简化外部代码。(简洁原则3)

 

    现在我们的形体类都只有形状信息。我们可以还希望给它增加密度信息,这样可以求出质量:

    class object{

    public:

        double density; //密度

        double mass(){ //返回质量

            return volume()*density;

        }

        virtual double volume();

    };

    注意到mass是“实函数”,并不用加virtual,因为对于每一种形体,计算质量的方法都是一样的:密度乘体积。所以我们不需要在cube类和sphere类中分别定义density和mass(),而是把它们放在基类object中定义,于是在子类中就都可以使用这些变量和函数了。所以,对于内部实现完全一致的类成员变量和函数,可以放在基类中统一实现,简化内部代码。(简洁原则4)

    于是我们看到,继承实际上分为两种,一种是实继承,继承的是内部实现,用于简化内部代码;一种是虚继承,继承的是对外接口,用于简化外部代码。

 

    但是,继承的时候一定要注意,当类B继承类A时,类B会全盘继承类A的变量、实函数和虚函数。所以,当且仅当类B确实需要A的所有变量、实函数和虚函数时,才可以让B继承A。如果B只需要A的一部分,应该让B和A继承自一个共同的基类。

    尽管面向对象的程序设计思想受到了很多人的推崇,但是程序中的类和现实中的事物毕竟不是完全对应。在现实中B属于A并不代表着B就必须继承自A。比如正方形属于长方形,但是让正方形类继承自长方形类并不是一种好的设计。正方形类只需要边长一个变量,而长方形却需要长和宽两个变量,让正方形全盘继承这两个变量是一种多余,而且还得采取必要的手段确保这两个变量始终相等。如果想要统一地对正方形和长方形求面积,不如让正方形和长方形共同继承一个可以求面积的虚函数:

    class has_area{

    public:

        virtual double area();

    }

    不过,让“带有颜色的长方形”去继承“长方形”就不会错,因为前者完全是后者的扩展,所以需要继承后者的所有信息。

 

    类的继承和多态,只有能够真正简化代码时,才是有用的。一些面向对象程序设计的教材总喜欢举一些不切实际的例子。比如商店里买东西,每一种不同的商品都继承自“商品”这个类,每种商品都定义了自己的getname()函数和getprice()函数来获取它的名称和价格。问题是我何必这么麻烦?我只需要一个商品类就可以了:

    class commodity{

    public:

        char name[10];

        double price;

    }

    然后每种不同的商品就是commodity类的不同实例,具有不同的name和price而已。没错,“牙刷”是一种“商品”,但“牙刷”不一定要成为“商品”的子类,它可以是“商品”类的一个实例,因为“牙刷”和其它的商品的区别只是数据不同(name和price都只是数据),而不是函数不同(立方体和球就是函数不同,因为它们求体积用的函数根本不一样)。仅仅是函数不同还不代表一定要用多态(因为使用函数指针也可以把不同的函数统一对待),还要求这里的函数不是全局函数而是类的实例成员函数,它和类的其它成员有着紧密的关联(比如立方体求体积与其边长有紧密关联),这种情况下使用多态是最合适的。TopLanguage上不时有GP(泛型编程)和OO(面向对象)之争,我认为全局函数用GP,如第2节所示,类的实例成员函数用OO,如本节所示,而类模板(或者说泛型类)则是GP与OO的结合。最后,Java没有函数指针或委托,所以Java只能使用多态。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值