C.129: When designing a class hierarchy, distinguish between implementation inheritance and interface inheritance
C.129:设计类层次关系时,区分实现继承和接口继承
Reason(原因)
Implementation details in an interface make the interface brittle; that is, make its users vulnerable to having to recompile after changes in the implementation. Data in a base class increases the complexity of implementing the base and can lead to replication of code.
接口如果包含实现细节就会变得脆弱;也就是说,实现部分变化之后,接口的用户经常需要重新编译。基类中的数据会增加基类实现的复杂性并引发代码的重复。
Note(注意)
Definition(定义):
A pure interface class is simply a set of pure virtual functions; see I.25.
纯虚接口类只是一组纯虚函数;参见I.25。
In early OOP (e.g., in the 1980s and 1990s), implementation inheritance and interface inheritance were often mixed and bad habits die hard. Even now, mixtures are not uncommon in old code bases and in old-style teaching material.
在早期的面向对象编程(例如1980年代到1990年代)中,实现继承和接口继承经常被混合使用,这样的恶习很难改掉。即使是现在,旧代码或者旧风格的培训资料中两种方式的混合体也会经常见到。
The importance of keeping the two kinds of inheritance increases
保持两种方式的继承的重要性可以随着以下因素的增长而增长:
Example, bad(反面示例)
class Shape { // BAD, mixed interface and implementation
public:
Shape();
Shape(Point ce = {0, 0}, Color co = none): cent{ce}, col {co} { /* ... */}
Point center() const { return cent; }
Color color() const { return col; }
virtual void rotate(int) = 0;
virtual void move(Point p) { cent = p; redraw(); }
virtual void redraw();
// ...
private:
Point cent;
Color col;
};
class Circle : public Shape {
public:
Circle(Point c, int r) :Shape{c}, rad{r} { /* ... */ }
// ...
private:
int rad;
};
class Triangle : public Shape {
public:
Triangle(Point p1, Point p2, Point p3); // calculate center
// ...
};
Problems(问题):
The implementation of Shape::move() is an example of implementation inheritance: we have defined move() once and for all for all derived classes. The more code there is in such base class member function implementations and the more data is shared by placing it in the base, the more benefits we gain - and the less stable the hierarchy is.
Shape::move()的实现是实现继承的一个例子:我们已经定义了所有派生类可用的move()。基类成员函数实现中的代码越多,为了共享而放入基类的数据越多,我们得到的好处也越多-当然继承关系的稳定性也越差。
Example(示例)
This Shape hierarchy can be rewritten using interface inheritance:
Shape继承关系可以按照接口继承方式重写:
class Shape { // pure interface
public:
virtual Point center() const = 0;
virtual Color color() const = 0;
virtual void rotate(int) = 0;
virtual void move(Point p) = 0;
virtual void redraw() = 0;
// ...
};
Note that a pure interface rarely has constructors: there is nothing to construct.
注意纯接口很少需要构造函数:没有任何东西需要构造。
class Circle : public Shape {
public:
Circle(Point c, int r, Color c) :cent{c}, rad{r}, col{c} { /* ... */ }
Point center() const override { return cent; }
Color color() const override { return col; }
// ...
private:
Point cent;
int rad;
Color col;
};
The interface is now less brittle, but there is more work in implementing the member functions. For example, center has to be implemented by every class derived from Shape.
这个接口脆弱性更少,但是实现成员函数的工作会更多。例如center需要所有继承自Shape的类分别实现。
Example, dual hierarchy(示例,双继承)
How can we gain the benefit of stable hierarchies from implementation hierarchies and the benefit of implementation reuse from implementation inheritance? One popular technique is dual hierarchies. There are many ways of implementing the idea of dual hierarchies; here, we use a multiple-inheritance variant.
我们如何既获得来自接口继承的稳定的继承关系的好处又获得来自实现继承的实现部分可重用的好处呢?一个常见的技术就是双继承。有多种方式实现双继承的想法,这里我们使用多重继承的版本。
First we devise a hierarchy of interface classes:
首先我们设计一个接口类的层次关系。
class Shape { // pure interface
public:
virtual Point center() const = 0;
virtual Color color() const = 0;
virtual void rotate(int) = 0;
virtual void move(Point p) = 0;
virtual void redraw() = 0;
// ...
};
class Circle : public virtual Shape { // pure interface
public:
virtual int radius() = 0;
// ...
};
To make this interface useful, we must provide its implementation classes (here, named equivalently, but in the Impl namespace):
为了让接口有用,我们必须提供它的实现类(这里,类名相同但是属于Impl命名空间)
class Impl::Shape : public virtual ::Shape { // implementation
public:
// constructors, destructor
// ...
Point center() const override { /* ... */ }
Color color() const override { /* ... */ }
void rotate(int) override { /* ... */ }
void move(Point p) override { /* ... */ }
void redraw() override { /* ... */ }
// ...
};
Now Shape is a poor example of a class with an implementation, but bear with us because this is just a simple example of a technique aimed at more complex hierarchies.
现在Shape作为包含实现的类例子有点简陋,但是请保持耐心,因为这个例子同样可以用于更复杂层次关系。
class Impl::Circle : public virtual ::Circle, public Impl::Shape { // implementation
public:
// constructors, destructor
int radius() override { /* ... */ }
// ...
};
And we could extend the hierarchies by adding a Smiley class (:-)):
接下来我们通过增加笑脸类扩展层次关系:
class Smiley : public virtual Circle { // pure interface
public:
// ...
};
class Impl::Smiley : public virtual ::Smiley, public Impl::Circle { // implementation
public:
// constructors, destructor
// ...
}
There are now two hierarchies:
现在这里有两种层次关系:
Since each implementation is derived from its interface as well as its implementation base class we get a lattice (DAG):
由于每个实现类既继承了接口也继承了实现基类,我们得到一个格子结构(有向无环图)。
Smiley -> Circle -> Shape
^ ^ ^
| | |
Impl::Smiley -> Impl::Circle -> Impl::Shape
As mentioned, this is just one way to construct a dual hierarchy.
如图所述,只有一种方式构建双继承。
The implementation hierarchy can be used directly, rather than through the abstract interface.
继承关系可以被直接使用,而不是通过抽象接口。
void work_with_shape(Shape&);
int user()
{
Impl::Smiley my_smiley{ /* args */ }; // create concrete shape
// ...
my_smiley.some_member(); // use implementation class directly
// ...
work_with_shape(my_smiley); // use implementation through abstract interface
// ...
}
This can be useful when the implementation class has members that are not offered in the abstract interface or if direct use of a member offers optimization opportunities (e.g., if an implementation member function is final)
这种做法在实现类需要没有包含在抽象接口中的成员,或者直接使用某个成员提供的优化机会(例如如果某个实现成员函数是final)时有用。
Note(注意)
Another (related) technique for separating interface and implementation is Pimpl.
分离接口和实现的另一个(相关的)技术是指向实现的指针。
Note(注意)
There is often a choice between offering common functionality as (implemented) base class functions and free-standing functions (in an implementation namespace). Base classes gives a shorter notation and easier access to shared data (in the base) at the cost of the functionality being available only to users of the hierarchy.
通常在提供通用功能时,需要在(已实现的)基类函数还是(在实现命名空间)独立函数这两种方式之间进行选择。通过基类实现的方式记法更简短,访问(基类中的)共有数据更容易。代价是这些功能只能被继承关系的用户使用。
Enforcement(实施建议)
原文链接:
https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#c129-when-designing-a-class-hierarchy-distinguish-between-implementation-inheritance-and-interface-inheritance
觉得本文有帮助?请分享给更多人。
关注【面向对象思考】轻松学习每一天!
面向对象开发,面向对象思考!