继承 — 抽象基类 (ABC)
将接口与实现分离有何重要意义?
接口是公司最宝贵的资源。设计接口所需的时间比快速实现一个满足该接口的具体类要长。此外,接口需要花费更多资深人员的时间。
既然接口如此宝贵,就应该保护它们,使其不被数据结构和其他实现细节所玷污。因此,您应该将接口与实现分离。
如何在 C++ 中(像 Modula-2 那样)将接口与实现分离?
使用抽象基类 (ABC)。
什么是 ABC?
抽象基类。
在设计层面,抽象基类 (ABC) 对应一个抽象概念。如果您问一个机械师他是否修理车辆,他可能会想您指的是哪种类型的车辆。他很可能不修理航天飞机、远洋客轮、自行车或核潜艇。问题是“车辆”这个词是一个抽象概念(例如,除非您知道要建造哪种车辆,否则您无法建造“车辆”)。在 C++ 中,`class Vehicle` 将是一个 ABC,`Bicycle`、`SpaceShuttle` 等是派生类(`OceanLiner` 是一种 `Vehicle`)。在现实世界的面向对象编程中,ABC 无处不在。
在编程语言层面,ABC 是一个包含一个或多个纯虚成员函数的 `class`。您无法创建 ABC 的对象(实例)。
什么是“纯虚”成员函数?
一个成员函数声明,它将一个普通类变成一个抽象类(即一个 ABC)。您通常只在派生类中实现它。
某些成员函数仅存在于概念中;它们没有任何合理的定义。例如,假设我要求您在位置 `(x,y)` 绘制一个大小为 7 的 `Shape`。您会问我“应该绘制哪种形状?”(圆形、正方形、六边形等绘制方式不同)。在 C++ 中,我们必须指出 `draw()` 成员函数的存在(以便用户在拥有 `Shape*` 或 `Shape&` 时可以调用它),但我们认识到它(逻辑上)只能在派生类中定义。
class Shape {
public:
virtual void draw() const = 0; // = 0 means it is "pure virtual"
// ...
};
这个纯虚函数使 `Shape` 成为一个 ABC。如果您愿意,可以将“`= 0;`”语法理解为代码位于 `NULL` 指针处。因此,`Shape` 向其用户承诺一项服务,但 `Shape` 无法提供任何代码来履行该承诺。这强制从 `Shape` 派生的任何[具体]类创建的实际对象都具有指定的成员函数,尽管基类还没有足够的信息来实际定义它。
请注意,可以为纯虚函数提供定义,但这通常会混淆新手,最好避免,直到以后再使用。
如何为包含指向(抽象)基类指针的类定义复制构造函数或赋值运算符?
如果该类“拥有”由(抽象)基类指针指向的对象,请在(抽象)基类中使用虚构造函数习语。与此习语一样,我们在基类中声明一个纯虚 `clone()` 方法。
class Shape {
public:
// ...
virtual Shape* clone() const = 0; // The Virtual (Copy) Constructor
// ...
};
然后我们在每个派生类中实现这个 `clone()` 方法。以下是派生类 `Circle` 的代码:
class Circle : public Shape {
public:
// ...
virtual Circle* clone() const;
// ...
};
Circle* Circle::clone() const
{
return new Circle(*this);
}
(注意:派生类中的返回类型有意与基类中的不同。)
以下是派生类 `Square` 的代码:
class Square : public Shape {
public:
// ...
virtual Square* clone() const;
// ...
};
Square* Square::clone() const
{
return new Square(*this);
}
现在假设每个 `Fred` 对象都“拥有一个” `Shape` 对象。自然,`Fred` 对象不知道 `Shape` 是 `Circle` 还是 `Square` 还是…… `Fred` 的复制构造函数和赋值运算符将调用 `Shape` 的 `clone()` 方法来复制对象。
class Fred {
public:
// p must be a pointer returned by new; it must not be NULL
Fred(Shape* p)
: p_(p) { assert(p != NULL); }
~Fred()
{ delete p_; }
Fred(const Fred& f)
: p_(f.p_->clone()) { }
Fred& operator= (const Fred& f)
{
if (this != &f) { // Check for self-assignment
Shape* p2 = f.p_->clone(); // Create the new one FIRST...
delete p_; // ...THEN delete the old one
p_ = p2;
}
return *this;
}
// ...
private:
Shape* p_;
};