Suppose we have a hierarchy of abstract classes:

/* Item | | ------- Object | |------ Link */ 

For example:

 class Item { public: World *world() = 0; }; //----------------------------------------------- class Object : public Item { public: virtual ObjectValue calculate() = 0; virtual void connectTo(const QString &objectId, const QString &linkDefId) = 0; }; //----------------------------------------------- class Link : public Item { public: virtual QString fromObjectId() const = 0; virtual QString toObjectId() const = 0; virtual QString linkDefId() const = 0; }; 

And here it is necessary to implement the Object and Link classes. For example, a family of client classes:

 class ClientObject : public Object { //... }; //----------------------------------------------- class ClientLink : public Link { //... }; 

and server class family:

 class ServerObject : public Object { //... }; //----------------------------------------------- class ServerLink : public Link { //... }; 

Then how to implement the World *Item::world() function, if it should work only inside the class of implantation Client[ClassName] and the same inside Client[ClassName] , but differ between the Server[ClassName] and Server[ClassName] ? (It is assumed that other families can be inherited and for each of them the World *Item::world() function should be implicitly the same for classes within this family.)

For example, in Java, the problem would be solved by using different words for the inheritance of implements and extentds . How can I get out of C ++? Of course, it is possible to describe ClientObject::world() and ClientLink::world() identically, but you understand that this is non-kosher.

  • If it is the same for all Client, then it is the same for Object and Link, right? (If not, then you would implement it in these classes in different ways, and there would be no question). And if it is the same there - well, then implement it in Item, what's stopping you? :) - Harry
  • @Harry Equally within the family, but differ between the families. That is, the ClientObject and ClientLink should be the same and the same for ServerObject and ServerLink , but, for example, between ClientXxx and ServerXxx will differ (I will edit the question to clarify this point) - asianirish
  • Then you are unlikely to avoid diamond shape - you have Object-Link and Client-Server differences ... Only here multiple inheritance is usually so rarely needed that it suggests redesign - does your relationship exactly ARE executed? Maybe there is enough to do with the attitude CONTAINS? Does such a complex hierarchy really reflect real phenomena or is it just chosen to combine the same code? - Harry
  • Create a class ClientItem and ServerItem, there implement your function in the necessary way, then inherit from these classes - Pavel Parshin
  • one
    @asianirish I didn’t understand at all what Client [ClassName] is and what "Client [ClassName] is and the same inside Client [ClassName]"? What is the difference between them? - Vlad from Moscow

1 answer 1

Option 1: If the implementation is a trivial one-line (for example, getName ()), then copy / paste is not a sin.

Option 2: Let's not separate the interface tree from the implementation inheritance tree. Those. implement Item::world right in Item.

Option 3: Implement the basic functionality of a given interface, in a separate object that is not inherited from the interface.

 class ImplItem { World *world(){ ... } }; class ClientLink : public Link, private ImplItem { public: using ImplItem::world; } 

Cons: using will have to be repeated for all functions of the interface. (copy / paste). Not all development environments adequately refactor this code (if you want to rename a function or change the argument list).

Option 4: Same as 3, but aggregation instead of inheritance:

 class ClientLink : public Link { ImplItem implItem; public: World *world() { return implItem.world(); } // copy/paste, но однострочная } 

Cons: copy / paste - more than option 3.

Option 5: Why do you need interface inheritance? For example:

 class Item { public: virtual World *world() = 0; }; class Object //: public Item { public: virtual Item* item() =0 // но, даже это может не понадобиться. или тривиально реализовываться: // { return dyndamic_cast<Item*>(this); } virtual ObjectValue calculate() = 0; virtual void connectTo(const QString &objectId, const QString &linkDefId) = 0; }; class ClientLink : public Link, private ClientItem {... }; 

Cons: interface inheritance is sometimes really necessary.

Option 6: Template implementation (Mixin-Based Programming)

 template<typename Base> class ImplItem : public Base { World *world(){ ... } }; class ServerObject : public ImplItem<Object> { //... }; 

cons: not all development environments adequately build the inheritance tree.

Option 7: Template implementation, ATL / WTL style (except for these two libraries, I have not seen anywhere else.) Unlike the previous version, ImplItem sees in its constructor a completely constructed object, i.e. can call ServerObjectPart virtual functions, which may be important with post-java habits.

 template<typename Base> class ImplItem : public Base { World *world(){ ... } }; class ServerObjectPart : public Object { //... World *world() НЕ реализована. }; typedef ImplItem<ServerObjectPart> ServerObject; //... new ServerObject(); 
  • Reception of impurity class is optimal here, thanks for the given direction! - asianirish