C++核心准则C.129:设计类层次关系时,区分实现继承和接口继承

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(定义):

  • interface inheritance is the use of inheritance to separate users from implementations, in particular to allow derived classes to be added and changed without affecting the users of base classes.

  • 接口继承是将继承用于使用户和实现隔离,尤其是允许在不影响使用基类的用户的前提下增加和修改派生类。

    扫描二维码关注公众号,回复: 10622103 查看本文章
  • implementation inheritance is the use of inheritance to simplify implementation of new facilities by making useful operations available for implementers of related new operations (sometimes called "programming by difference").

  • 实现继承是将继承用于简化新功能的实现,方式是让相关新操作的实现者可以访问有用的操作(又被称为“根据差异编程”)。

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

保持两种方式的继承的重要性可以随着以下因素的增长而增长:

  • with the size of a hierarchy (e.g., dozens of derived classes),

  • 继承体系的大小(例如,几十个派生类),

  • with the length of time the hierarchy is used (e.g., decades), and

  • 继承关系被使用的时间跨度(例如数十年),和

  • with the number of distinct organizations in which a hierarchy is used (e.g., it can be difficult to distribute an update to a base class)

  • 使用继承体系的组织的数量(例如分发基类的更新会别变得困难)

                                                              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(问题):

  • As the hierarchy grows and more data is added to Shape, the constructors get harder to write and maintain.

  • 随着继承关系的成长,更多的数据需要增加到Shape类,构造函数会越来越难以编写和维护。

  • Why calculate the center for the Triangle? we may never use it.

  • 为什么计算三角形的中心?我们可能永远不会用到它。

  • Add a data member to Shape (e.g., drawing style or canvas) and all classes derived from Shape and all code using Shape will need to be reviewed, possibly changed, and probably recompiled.

  • 增加Shape的数据成员(例如描画风格或者画布)意味着所有继承自Shape的派生类和所有使用Shape的代码都要被确认,可能需要修改,几乎一定需要重新编译。

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:

现在这里有两种层次关系:

  • interface: Smiley -> Circle -> Shape

  • 接口继承:Smiley -> Circle -> Shape

  • implementation: Impl::Smiley -> Impl::Circle -> Impl::Shape

  • 实现继承:Impl::Smiley -> Impl::Circle -> Impl::Shape

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(实施建议)

  • Flag a derived to base conversion to a base with both data and virtual functions (except for calls from a derived class member to a base class member)

  • 标记将派生类转换为既包含数据又包含虚函数的基类的情况。从派生类成员函数调用基类成员函数除外。

  • ???

                                                 原文链接:

https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#c129-when-designing-a-class-hierarchy-distinguish-between-implementation-inheritance-and-interface-inheritance


觉得本文有帮助?欢迎点赞并分享给更多的人。

阅读更多更新文章,请关注微信公众号【面向对象思考】

发布了410 篇原创文章 · 获赞 677 · 访问量 31万+

猜你喜欢

转载自blog.csdn.net/craftsman1970/article/details/105182964
今日推荐