C++核心準則C.129:設(shè)計類層次關(guān)系時,區(qū)分實現(xiàn)繼承和接口繼承?
C.129: When designing a class hierarchy, distinguish between implementation inheritance and interface inheritance
C.129:設(shè)計類層次關(guān)系時,區(qū)分實現(xiàn)繼承和接口繼承?
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.
接口如果包含實現(xiàn)細節(jié)就會變得脆弱;也就是說,實現(xiàn)部分變化之后,接口的用戶經(jīng)常需要重新編譯。基類中的數(shù)據(jù)會增加基類實現(xiàn)的復雜性并引發(fā)代碼的重復。
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.
接口繼承是將繼承用于使用戶和實現(xiàn)隔離,尤其是允許在不影響使用基類的用戶的前提下增加和修改派生類。
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").
實現(xiàn)繼承是將繼承用于簡化新功能的實現(xiàn),方式是讓相關(guān)新操作的實現(xiàn)者可以訪問有用的操作(又被稱為“根據(jù)差異編程”)。
A pure interface class is simply a set of pure virtual functions; see?I.25.
純虛接口類只是一組純虛函數(shù);參見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.
在早期的面向?qū)ο缶幊?例如1980年代到1990年代)中,實現(xiàn)繼承和接口繼承經(jīng)常被混合使用,這樣的惡習很難改掉。即使是現(xiàn)在,舊代碼或者舊風格的培訓資料中兩種方式的混合體也會經(jīng)常見到。
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
繼承關(guān)系被使用的時間跨度(例如數(shù)十年),和
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)
使用繼承體系的組織的數(shù)量(例如分發(fā)基類的更新會別變得困難)
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.
隨著繼承關(guān)系的成長,更多的數(shù)據(jù)需要增加到Shape類,構(gòu)造函數(shù)會越來越難以編寫和維護。
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的數(shù)據(jù)成員(例如描畫風格或者畫布)意味著所有繼承自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()的實現(xiàn)是實現(xiàn)繼承的一個例子:我們已經(jīng)定義了所有派生類可用的move()?;惓蓡T函數(shù)實現(xiàn)中的代碼越多,為了共享而放入基類的數(shù)據(jù)越多,我們得到的好處也越多-當然繼承關(guān)系的穩(wěn)定性也越差。
Example(示例)
This Shape hierarchy can be rewritten using interface inheritance:
Shape繼承關(guān)系可以按照接口繼承方式重寫:
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.
注意純接口很少需要構(gòu)造函數(shù):沒有任何東西需要構(gòu)造。
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.
這個接口脆弱性更少,但是實現(xiàn)成員函數(shù)的工作會更多。例如center需要所有繼承自Shape的類分別實現(xiàn)。
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.
我們?nèi)绾渭全@得來自接口繼承的穩(wěn)定的繼承關(guān)系的好處又獲得來自實現(xiàn)繼承的實現(xiàn)部分可重用的好處呢?一個常見的技術(shù)就是雙繼承。有多種方式實現(xiàn)雙繼承的想法,這里我們使用多重繼承的版本。
First we devise a hierarchy of interface classes:
首先我們設(shè)計一個接口類的層次關(guān)系。
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):
為了讓接口有用,我們必須提供它的實現(xiàn)類(這里,類名相同但是屬于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.
現(xiàn)在Shape作為包含實現(xiàn)的類例子有點簡陋,但是請保持耐心,因為這個例子同樣可以用于更復雜層次關(guān)系。
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 (:-)):
接下來我們通過增加笑臉類擴展層次關(guān)系:
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:
現(xiàn)在這里有兩種層次關(guān)系:
interface: Smiley -> Circle -> Shape
接口繼承:Smiley -> Circle -> Shape
implementation: Impl::Smiley -> Impl::Circle -> Impl::Shape
實現(xiàn)繼承: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):
由于每個實現(xiàn)類既繼承了接口也繼承了實現(xiàn)基類,我們得到一個格子結(jié)構(gòu)(有向無環(huán)圖)。
Smiley?????->?????????Circle?????->??Shape
??^?????????????????????^???????????????^
??|?????????????????????|???????????????|
Impl::Smiley?->?Impl::Circle?->?Impl::Shape
As mentioned, this is just one way to construct a dual hierarchy.
如圖所述,只有一種方式構(gòu)建雙繼承。
The implementation hierarchy can be used directly, rather than through the abstract interface.
繼承關(guān)系可以被直接使用,而不是通過抽象接口。
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)
這種做法在實現(xiàn)類需要沒有包含在抽象接口中的成員,或者直接使用某個成員提供的優(yōu)化機會(例如如果某個實現(xiàn)成員函數(shù)是final)時有用。
Note(注意)
Another (related) technique for separating interface and implementation is?Pimpl.
分離接口和實現(xiàn)的另一個(相關(guān)的)技術(shù)是指向?qū)崿F(xiàn)的指針。
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.
通常在提供通用功能時,需要在(已實現(xiàn)的)基類函數(shù)還是(在實現(xiàn)命名空間)獨立函數(shù)這兩種方式之間進行選擇。通過基類實現(xiàn)的方式記法更簡短,訪問(基類中的)共有數(shù)據(jù)更容易。代價是這些功能只能被繼承關(guān)系的用戶使用。
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)
標記將派生類轉(zhuǎn)換為既包含數(shù)據(jù)又包含虛函數(shù)的基類的情況。從派生類成員函數(shù)調(diào)用基類成員函數(shù)除外。
???
原文鏈接:
https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#c129-when-designing-a-class-hierarchy-distinguish-between-implementation-inheritance-and-interface-inheritance
覺得本文有幫助?請分享給更多人。
關(guān)注【面向?qū)ο笏伎肌枯p松學習每一天!
面向?qū)ο箝_發(fā),面向?qū)ο笏伎迹?/p>

