条款34、区分接口继承和实现继承

来源:互联网 发布:录音整理软件 免费 编辑:程序博客网 时间:2024/05/23 01:53
public继承由两部分组成:函数接口继承和函数实现继承设计类时,我们有不同的需求:1、Derived只继承成员函数的接口(也就是声明) 2、Derived同时继承接口和实现,但又能覆写继承的实现(override)  3、Derived同时继承接口和实现,但不允许覆写任何东西。为表现上面的差异,考虑以下代码:
class Shape{public:         virtual void draw() const=0;         virtual void error(const std::string& msg);         int objectID() const;         ….};class Rectangle: public Shape{….};class Eclipse:public Shape{….};

Shape是个抽象类,因为具有纯虚函数draw。故只能创建其派生类实体。尽管如此,Shape还是强烈影响了所有以public形式继承它的派生类。
a、成员函数接口总是会被继承。如TK32,可施行于基类的函数也可施行于派生类中。
声明的三个不同函数:draw 纯虚   error 虚函数  objectID非虚函数。首先考虑draw:
class Shape{public:         virtualvoid draw() const=0;         ….};

pure virtual两个最突出的特性:1)、必须被任何“继承了它们”的具象类重新声明  2)、在抽象类的通常没有定义。结合这两性质得出:
1、声明为纯虚函数的目的是为了让派生类只继承其接口
这对Shape::draw再合理不过,因为任何shape对象都应该是可绘出的。相当于对设计者说:你必须提供一个draw函数,但我不干涉你怎么实现它。
意外的是,我们也可以为纯虚函数提供定义。编译器也不会报错,但调用的唯一途径是:调用时明确指出其class。如下:
Shape* ps=new Shape;   //F  Shape为抽象的Shape* ps1=new Rectangle; //Tps1->draw();     //T 调用Rectangle::drawShape* ps2=new Eclipse; //Tps2->draw();   //T   Eclipse::drawps1->Shape::draw(); //T  Shape::drawps2->Shape::draw(); //T  Shape::draw

非纯虚函数和纯虚函数有点点不同:派生类继承其函数接口,但impure virtual函数会提供一份实现代码。派生类可能覆写它。
!声明非纯虚函数的目的,是让派生类继承该函数的接口和缺省实现。考虑Shape::error
class Shape{public:         virtualvoid error(const std::string& msg);         ….};

接口表示:每个类必须支持一个error函数,但可自由处理错误。如果不想做出任何特殊行为,可以调用Shape类提供的缺省版本
允许非纯虚函数同时指定函数声明和缺省行为,却也可能造成危险。考虑以下例子:如下飞机继承体系,A 、B两种飞机以相同方式飞行。
class Airport{….};  //机场clss Airplane{<span style="white-space:pre"></span>public:<span style="white-space:pre"></span>virtual void fly virtual void fly(const Airport&  des);};void fly{const Airport&  des}

{缺省行为,飞至指定目的地 ;}
class A: public Airplane{….};
class B: public Airplane{….};
所有飞机能飞,但需要不同表现,Airplane:fly声明为virtual。避免AB中代码重复,提供缺省代码,同时被AB继承。这是典型的面向对象设计:将共同性质至于其类。突出共性,避免代码重复
现在引进C类飞机,和AB飞行方式不同,但急于上线新飞机,竟忘记定义其fly,于是出现下面代码:
class C: public Airplane{….}; //未声明flyAirport*  P;   //机场PAirplane* Pa=new C;pa->fly(p;)   //调用Airplane::fly

很明显这会造成灾难,问题不在于缺省行为,而是在未说明“需要”的情况下就继承了该行为。有简单的方法可以解决此问题,即分离虚函数接口和缺省实现。如下代码:
class Airport{….};  //机场clss Airplane{public:         virtual void fly(const  Airport&  des) =0;         …protected:         void defaultFly(const  Airport& des);};

void  Airplane::defaultFly(const  Airport& des){缺省行为,飞至指定目的地 ;}
需要注意的是,Airplane::fly已声明为一个纯虚函数,只提供飞行接口。缺省行为也出现在类中。AB若想使用此行为,可以在fly中做一个inline调用。如下:
class A: public Airplane{public:         virtualvoid fly(const Airport& des)         {Airplane:: defaultFly(des); }         …}class B: public Airplane{public:         virtualvoid fly(const Airport& des)         {Airplane:: defaultFly(p); }         ….}

这样C就不可能意外继承函数体了,因为纯虚函数迫使C必须提供自己的fly版本。
class C: public Airplane{public:         virtual void fly(constAirport& des)         ….}void C::fly(const Airport& des){自定义版本;};


Airplane::defaultFly版本被声明protected,因为它是Aiplane和其派生类的实现细节,我们只在间能不能飞而不会在意怎么飞。同时Airplane::defaultFly是非虚函数也很重要,因为没有任何派生类应该重新定义此函数。如果定义为虚函数,又会出现一个循环问题:如果派生类又忘记重新定义defaultFly会怎样?
有些人反对以不同的函数分别提供接口和实现(过度相同的函数引起命名空间污染)。但同意接口和实现应该分开。我们可以利用:纯虚函数必须在Drived类中重新声明,但它们也可以有自己的实现。如下代码:
<pre name="code" class="cpp">class Airport{….};   //机场clss Airplane{public:virtual void fly(const  Airport&  des) =0;…};void Airplane::fly(const Airport& des)   //纯虚函数的实现{缺省行为,飞向指定目的地;}class A: public Airplane{public:virtual void fly(const Airport& des){Airplane::fly(des); }…}class B: public Airplane{public:virtual void fly(const Airport& des){ Airplane::fly(des);; }….};class C: public Airplane{public:virtual void fly(const Airport& des)….}void C::fly(const Airport& des){自定义飞行行为;}


这个设计和前面基本一样,只是pure virtual函数Airplane::fly代替了独立函数Airplane::defaultFly.本质上分为两要素:声明部分是接口,定义部分是缺省实现。
再看Shape的non-virtual函数:
class Shape{public:         int objectID() const;…};

成员函数是非虚函数,意味着并不打算在派生类中有不同行为。实际上非虚成员函数表现的不变性凌驾其特意性。
声明非虚函数的目的:让Drived类继承函数接口和一份强制实现。
如objectID函数,每个对象都有产生识别码的函数,且计算方法相同。任何派生类不应去改变此行为,所以它不应被重定义。纯虚、虚、非虚三类函数使得能精确指定想要派生类继承的东西。但也会犯一些错误:1、将所有函数声明为非虚,这使得派生类没机会做特化工作。想作为基类的任何类,都会有若干虚函数。如果担心虚函数成本,2-8法则说明,平均而言函数调用中有80%是虚函数不冲击的大体效率。应将精力先至于20%的关键代码上。2、另一个常见错误是全部声明为虚函数。当然有些是正确的,如接口类。这样设计时要确定不变性凌驾于特意性。
需要记住的:1、接口继承和实现继承不同。在public继承之下,派生类总是继承基类的接口。2、纯虚函数只具体指定接口继承。3、非纯虚函数具体指定接口继承和缺省实现继承。4、非虚函数具体指定接口继承和强制实现继承。

0 0
原创粉丝点击