實現繼承和接口繼承


實現繼承和接口繼承

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.
  • 接口繼承是將繼承用於使用戶和實現隔離,尤其是允許在不影響使用基類的用戶的前提下增加和修改派生類。
  • 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(反面示例)

classShape{//BAD,mixedinterfaceandimplementationpublic:      Shape();      Shape(Pointce={0,0},Colorco=none):cent{ce},col{co}{/*...*/}      Pointcenter()const{returncent;}      Colorcolor()const{returncol;}      virtualvoidrotate(int)=0;      virtualvoidmove(Pointp){cent=p;redraw();}      virtualvoidredraw();      //...private:      Pointcent;      Colorcol;};classCircle:publicShape{public:      Circle(Pointc,intr):Shape{c},rad{r}{/*...*/}      //...private:      intrad;};classTriangle:publicShape{public:     Triangle(Pointp1,Pointp2,Pointp3);     //calculatecenter     //...};

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繼承關係可以按照接口繼承方式重寫:

classShape{//pureinterfacepublic:      virtualPointcenter()const=0;      virtualColorcolor()const=0;      virtualvoidrotate(int)=0;      virtualvoidmove(Pointp)=0;      virtualvoidredraw()=0;      //...};

Note that a pure interface rarely has constructors: there is nothing to construct.

注意純接口很少需要構造函數:沒有任何東西需要構造。

classCircle:publicShape{public:      Circle(Pointc,intr,Colorc):cent{c},rad{r},col{c}{/*...*/}      Pointcenter()constoverride{returncent;}      Colorcolor()constoverride{returncol;}      //...private:      Pointcent;      intrad;      Colorcol;};

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:

首先我們設計一個接口類的層次關係。

classShape{//pureinterfacepublic:      virtualPointcenter()const=0virtualColorcolor()const=0;virtualvoidrotate(int)=0;virtualvoidmove(Pointp)=0;virtualvoidredraw()=0;//...};classCircle:publicvirtualShape{//pureinterfacepublic:virtualintradius()=0;//...};

To make this interface useful, we must provide its implementation classes (here, named equivalently, but in the Impl namespace):

為了讓接口有用,我們必須提供它的實現類(這裡,類名相同但是屬於Impl命名空間)

classImpl::Shape:publicvirtual::Shape{//implementationpublic:      //constructors,destructor      //...     Pointcenter()constoverride{/*...*/}     Colorcolor()constoverride{/*...*/}     voidrotate(int)override{/*...*/}     voidmove(Pointp)override{/*...*/}     voidredraw()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作為包含實現的類例子有點簡陋,但是請保持耐心,因為這個例子同樣可以用於更復雜層次關係。

classImpl::Circle:publicvirtual::Circle,publicImpl::Shape{//implementationpublic:      //constructors,destructor      intradius()override{/*...*/}      //...};

And we could extend the hierarchies by adding a Smiley class (:-)):

接下來我們通過增加以惡笑臉類擴展層次關係:

classSmiley:publicvirtualCircle{//pureinterfacepublic:      //...};  classImpl::Smiley:publicvirtual::Smiley,publicImpl::Circle{//implementationpublic:      //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.

繼承關係可有被直接使用,而不是通過抽象接口。

voidwork_with_shape(Shape&);intuser(){       Impl::Smileymy_smiley{/*args*/};     //createconcreteshape       //...       my_smiley.some_member();//useimplementationclassdirectly       //...       work_with_shape(my_smiley);//useimplementationthroughabstractinterface       //...}

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


覺得本文有幫助?請分享給更多人。

面向對象開發,面向對象思考!


分享到:


相關文章: