[翻译] Effective C++, 3rd Edition, Item 34: 区分 inheritance of interface(接口继承)和 inheritance of implementation(实现继承)(上)

Item 34: 区分 inheritance of interface(接口继承)和 inheritance of implementation(实现继承)

作者:Scott Meyers

译者:fatalerror99 (iTePub's Nirvana)

发布:http://blog.csdn.net/fatalerror99/

(public) inheritance 这个表面上简单易懂的观念,一旦被近距离审视,就会被证明是由两个相互独立的部分组成的:inheritance of function interfaces(函数接口的继承)和 inheritance of function implementations(函数实现的继承)。这两种 inheritance 之间的差异正好符合本书 Introduction 中论述的 function declarations(函数声明)和 function definitions(函数定义)之间的差异。

作为一个 class 的设计者,有的时候你想要 derived classes 只继承一个 member function 的 interface (declaration)。有的时候你想要 derived classes 既继承 interface(接口)也继承 implementation(实现),但你要允许它们替换他们继承到的 implementation。还有的时候你想要 derived classes 继承一个函数的 interface(接口)和 implementation(实现),而不允许它们替换任何东西。

为了更好地感觉这些选择之间的不同之处,考虑一个在图形应用程序中表示几何图形的 class hierarchy(类继承体系):

class Shape {
public:
  virtual void draw() const = 0;

  virtual void error(const std::string& msg);

  int objectID() const;

  ...
};

class Rectangle: public Shape { ... };

class Ellipse: public Shape { ... };

Shape 是一个 abstract class(抽象类),它的 pure virtual function(纯虚拟函数)表明了这一点。作为结果,客户不能创建 Shape class 的实例,只能创建从它继承的 classes 的实例。但是,Shape 对所有从它(公有)继承的类施加了非常强大的影响,因为

  • 成员函数 interfaces are always inherited。就像 Item 32 解释的,public inheritance 意味着 is-a,所以对一个 base class 来说成立的任何东西,对于它的 derived classes 也必须成立。因此,如果一个函数适用于一个 class,它也一定适用于它的 derived classes。

Shape class 中声明了三个函数。第一个,draw,在一个明确的显示设备上画出当前对象。第二个,error,当有一个错误需要报告时(此处原文有误,根据作者网站勘误修改——译者注),就调用它。第三个,objectID,返回当前对象的唯一整型标识符。每一个函数都用不同的方式声明:draw 是一个 pure virtual function(纯虚拟函数);error 是一个 simple (impure?) virtual function(简单虚拟函数);而 objectID 是一个 non-virtual function(非虚拟函数)。这些不同的声明暗示了什么呢?

考虑第一个 pure virtual function(纯虚拟函数)draw

class Shape {
public:
  virtual void draw() const = 0;
  ...
};

pure virtual functions(纯虚拟函数)的两个最显著的特性是它们必须被任何继承它们的具体类重新声明,和抽象类中一般没有它们的定义。把这两个特性加在一起,你应该认识到

  • 声明一个 pure virtual function(纯虚拟函数)的目的是使 derived classes 继承一个函数 interface only

这就使 Shape::draw function 具有了完整的意义,因为它要求所有的 Shape 对象必须能够画出来是合情合理的,但是 Shape class 本身不能为这个函数提供一个合乎情理的缺省的实现。例如,画一个椭圆的算法和画一个矩形的算法是非常不同的,Shape::draw 的声明告诉具体 derived classes 的设计者:“你必须提供一个 draw function,但是我对于你如何实现它不发表意见。”

顺便提一句,为一个 pure virtual function(纯虚拟函数)提供一个定义是有可能的。也就是说,你可以为 Shape::draw 提供一个实现,而 C++ 也不会抱怨什么,但是调用它的唯一方法是用 class name 限定修饰这个调用:

Shape *ps = new Shape;              // error! Shape is abstract

Shape *ps1 = new Rectangle;         // fine
ps1->draw();                     // calls Rectangle::draw

Shape *ps2 = new Ellipse;           // fine
ps2->draw();                     // calls Ellipse::draw

ps1->Shape::draw();                 // calls Shape::draw

ps2->Shape::draw();                 // calls Shape::draw

除了帮助你在鸡尾酒会上给同行程序员留下印象外,这个特性通常没什么用处,然而,就像下面你将看到的,它能用来作为一个“为 simple (impure) virtual functions 提供一个 safer-than-usual 的实现”的机制。

simple virtual functions 背后的故事和 pure virtuals 有一点不同。derived classes 照常还是继承函数的 interface,但是 simple virtual functions 提供了一个可以被 derived classes 替换的实现。如果你为此考虑一阵儿,你就会认识到

  • 声明一个 simple virtual function 的目的是让 derived classes 继承一个函数 interface as well as a default implementation

考虑 Shape::error 的情况:

class Shape {
public:
  virtual void error(const std::string& msg);
  ...
};

interface 要求每一个 class 必须支持一个在遭遇到错误时被调用的函数,但是每一个 class 可以自由地用它觉得合适的任何方法处理错误。如果一个 class 不需要做什么特别的事情,它可以仅仅求助于 Shape class 中提供的错误处理的缺省版本。也就是说,Shape::error 的声明告诉 derived classes 的设计者:“你应该支持一个 error function,但如果你不想自己写,你可以求助 Shape class 中的缺省版本。”

结果是:允许 simple virtual functions 既指定一个函数接口又指定一个缺省实现是危险的。来看一下为什么,考虑一个 XYZ 航空公司的飞机的 hierarchy(继承体系)。XYZ 只有两种飞机,Model A 和 Model B,它们都严格地按照同样的方法飞行。于是,XYZ 设计如下 hierarchy(继承体系):

class Airport { ... };                     // represents airports

class Airplane {
public:
  virtual void fly(const Airport& destination);

  ...

};

void Airplane::fly(const Airport& destination)
{
  default code for flying an airplane to the given destination
}

class ModelA: public Airplane { ... };

class ModelB: public Airplane { ... };

为了表述所有的飞机必须支持一个 fly 函数,并为了“不同机型可能(在理论上)需要不同的对 fly 的实现”的事实,Airplane::fly 被声明为 virtual。然而,为了避免在 ModelAModelB classes 中些重复的代码,缺省的飞行行为由 Airplane::fly 的函数体提供,供 ModelAModelB 继承。

这是一个经典的 object-oriented 设计。因为两个 classes 共享一个通用特性(它们实现 fly 的方法),所以这个通用特性就被转移到一个 base class 之中,并由两个 classes 来继承这个特性。这个设计使得通用特性变得清楚明白,避免了代码重复,提升了未来的可扩展性,简化了长期的维护——因为 object-oriented 技术,所有这些东西都受到很高的追捧。XYZ 航空公司应该引以为荣。

(点击此处,接下篇)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值