I come, I see, I conquer

                    —Gaius Julius Caesar

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::


1. 类的封装特性

对于软件设计而言,为了尽量避免某个模块的行为干扰同一系统中其他模块,应该让模块仅仅公开必须让外界知道的内容,而隐藏其他一切内容。“信息隐藏”这种设计理论产生了C++类的封装特性。要注意的是,我们不能滥用类的封装特性,把毫不相干的数据和函数封装到类里头。

class WhoAmI
{
public:
    
void GetMyName(void); //名字是可以公开的
protected:
    
void GetMyAsset(void); //财产是受保护的,只有我和继承者可以使用
private:
    
void GetMyGuilty(void); //罪过是要保密的,只有我自己才能偷看
};



2. 类的继承特性

对象是类一个实例(Instance)。面向对象设计的重点是类的设计,而不是对象的设计。对于C++程序而言,设计孤立的类是比较容易的,难的是正确设计基类及其派生类。

class A
{
public:
    
void Func1(void);
    
void Func2(void);
};

class B : public A
{
public:
    
void Func3(void);
    
void Func4(void);
};

void main()
{
    B b;
    b.Func1(); 
//B从A继承了函数Func1
    b.Func2(); //B从A继承了函数Func2
    b.Func3();
    b.Func4();
}


这个简单的实例说明C++的继承特性可以提高程序的可复用性。为防止乱用继承,我们应该给继承立一些使用规则:
规则2.1: 如果类A和类B毫不相关,不可以为了使B的功能更多些而让B继承A的功能和属性。
规则2.2: 如果逻辑上B是A的一种(a kind of),则允许B继承A的功能和属性。例如男人(man)是人(human)的一种,男孩(boy)是男人(man)的一种,那么类Man可以从类Human派生,类Boy可以从类Man派生。

class Human //Human是基类
{
    
};

class Man : public Human //Man是Human的派生类
{
    
};

class Boy : public Man //Boy是Man的派生类
{
    
};


但继承的概念在程序世界与现实世界并不完全相同。如:从生物学角度来讲,鸵鸟是鸟的一种,鸟能飞,但鸵鸟不能飞。再例如:从数学角度讲,圆是一种特殊的椭圆,按理来说,圆应该可以从椭圆派生,但是椭圆有长轴和短轴之分,如果继承,岂非画蛇添足?所以更加严格的继承规则应该是:
规则2.3: 若在逻辑上B是A的一种(a kind of),并且A的所有功能和属性对B而言都有意义,则允许B继承A的功能和属性。


3. 类的组合特性

组合(composition)用于表示类的"整体/部分"的关系。例如主机、显示器、键盘、鼠标组合成一台计算机。继承则表示类的"一般/特殊"关系。
规则3.1: 若在逻辑上A是B的一部分(a part of),则不允许B从A派生,而是要用A和其他东西组合出B。
例如眼(Eye)、鼻(Nose)、口(Mouth)、耳(Ear)是头(Head)的一部分,所以类Head应该由类Eye、Nose、Mouth、Ear组合而成,不是派生而成。

class Eye
{
public:
    
void Look(void);
};

class Nose
{
public:
    
void Smell(void);
};

class Mouth
{
public:
    
void Eat(void);
};

class Ear
{
public:
    
void Listen(void);
};

//正确的设计,虽然代码冗长
class Head
{
public:
    
void Look(void) { my_eye.Look(); }
    
void Smell(void) { my_nose.Smell(); }
    
void Eat(void) { my_mouth.Eat(); }
    
void Listen(void) { my_ear.Listen(); }
private:
    Eye my_eye;
    Nose my_nose;
    Mouth my_mouth;
    Ear my_ear;
};

 

如果允许Head从Eye、Nose、Mouth、Ear派生而成,那么Head将自动具有Look、Smell、Eat、Listen这些功能。程序如下:

class Head : public Eye, public Nose, public Mouth, public Ear
{

};


采用继承方法来实现的Head程序十分简短并且运行正确,但是这种设计却是不对的。


4. 类的动态特性

在绝大多数情况下,程序的功能是在编译的时候就确定下来了,我们称为静态特性。反之,如果程序的功能是在运行时刻才确定下来的,称为动态特性。C++的虚函数、抽象基类、动态联编和多态性(polymorphism)构成了出色的动态特性。

4.1 虚函数

假定几何形状的基类为Shape,其派生类有Circle、Rectangle、Ellipse等,每个派生类都能够绘制自己的形状。不管形状如何,我们希望用统一的方式来调用绘制函数,最好是使用Shape定义的函数接口Draw,并让程序在运行时动态地确定应该使用哪个派生类的Draw函数。

为了使这种行为可行,我们把基类Shape中的函数Draw声明为虚函数,然后在派生类中重新定义Draw使之绘制正确的形状。

一旦类的一个函数被声明为虚函数,那么其派生类的对应函数也成为虚函数。虽然函数在类层次结构的高层中声明为虚函数将会使它在底层自动(隐式)地成为虚函数,但是为了提高程序的清晰性,建议在每一层中将它显式地声明为虚函数(即加virtual)。例如:

class Shape
{
public:
    
virtual void Draw(void); //Draw为虚函数
};

class Rectangle : public Shape
{
public:
    
virtual void Draw(void); //Draw为虚函数
};



4.2 抽象基类

当我们把类看做是一种数据类型时,通常会认为该类肯定是要被实例化为对象的。但是在很多情况下,定义那些不被实例化为对象的类是很有用的,这种类称为抽象类(Abstract Class)。能够被实例化为对象的类称为具体类(Concrete Class)。抽象为在的唯一目的就是让其派生类继承它的函数接口,因此它通常也被称为抽象基类(Abstract Base Class)。如果将基类的虚函数声明为纯虚函数,那么该类就成为抽象基类。纯虚函数是在声明时其"初始化值"为0的函数。例如:

class Shape //Shape是抽象基类
{
public:
    
virtual void Draw(void= 0//Draw为纯虚函数
};


抽象基类Shape的纯虚函数Draw根本不知道它自己能干什么,具体功能必须由派生类的Draw函数来实现。
很多良好的面向对象系统中,基类层次结构的顶部通常是抽象基类,甚至可以有好几层的抽象类。


4.3 动态联编

如果将基类Shape的函数Draw声明为virtual,然后用指向派生类对象的基类指针调用Draw,那么程序会动态地(即在运行时)选择该派生类的Draw函数,这种特性称为动态联编。例如:

Shape *aShape; //定义指向基类的指针

Circle aCircle; 
//定义派生类对象
Cube aCube; //定义派生类对象
Sphere aSphere; //定义派生类对象

aShape
=&aCircle; //将基类指针指向派生类
aShape->Draw(); //绘制一个circle

aShape
=&aCube; //将基类指针指向派生类
aShape->Draw(); //绘制一个cube

aShape
=&aSphere; //将基类指针指向派生类
aShape->Draw(); //绘制一个sphere

    动态联编可以使独立软件供应商(ISV)在不透露技术秘密的情况下发行软件包,即只发行头文件和二进制目标码,不必公开源代码。软件开发者可以利用继承机制从ISV提供的类库中派生出新的类。和ISV类库一起运行的软件也能够和新的派生类一起运行,并且能够通过动态联编使用新派生类的虚函数。


4.4 多态性

当许多派生类因为继承了共同的基类而发生关系时,每一个派生类的对象都可以被当成基类的对象来使用。这些派生类对象能对同一函数调用做出不同的反应,这就是多态性。多态性是通过虚函数和动态联编实现的。例如:

#include <iostream>
using namespace std;

class Shape
{
public:
    
virtual void Draw(void) { cout<<"Draw a Shape!"<<endl; }
};

class Circle : public Shape
{
public:
    
virtual void Draw(void) { cout<<"Draw a Circle!"<<endl; }
};

class Cube : public Shape
{
public:
    
virtual void Draw(void) { cout<<"Draw a Cube!"<<endl; }
};

class Sphere : public Shape
{
public:
    
virtual void Draw(void) { cout<<"Draw a Sphere!"<<endl; }
};

void Draw(Shape *aShape) //多态函数(全局函数) 
{
    aShape
->Draw();
}

void main()
{
    Circle aCircle;
    Cube aCube;
    Sphere aSphere;

    Draw(
&aCircle); //绘制一个circle
    Draw(&aCube); //绘制一个cube
    Draw(&aSphere); //绘制一个sphere
}


输出结果:
Draw a Circle!
Draw a Cube!
Draw a Sphere!

综合C++的"虚函数"和"多态",有如下突出优点
 ☆ 应用程序不必为每一个派生类编写功能调用,只需要对基类的虚函数进行处理即可。可以大大提高程序的可复用性和 可扩展性。
 ☆ 派生类的功能可以被基类指针引用,这叫向后兼容。以前写的程序可以被将来写的程序调用不足为奇,但是将来的程序可以被以前写的程序调用那可了不起,这正是动态特性的妙处。

注:本文摘自林锐的<<高质量程序设计指南-C++/C语言>>

posted on 2008-02-26 11:29  jcsu  阅读(1066)  评论(0编辑  收藏  举报