类的封装特性、继承特性、组合特性和动态特性


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语言>>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值