How do I define containers that inherit each other if the contained objects inherit too? (With QObject as base)

StackOverflow https://stackoverflow.com/questions/14401522

Question

Background:

  • I have my class called ObjectListModel which inherits QAbstractListModel and contains a QObjectList. The objects are rows and their properties are columns (set using a QMetaObject), and notifcation changes are propagated to views. There's also some container helpers (begin/end/iterator/size) so that I can iterate through the QObject's stored.
  • I also have a TypedObjectListModel<T>, which provides type-safety (mainly by overriding push_back et.al. with and defining new iterator types that do static_cast to T).

This all works very well when I only have one type of objects. I just create new class (f.ex. FruitsModel, which has Q_OBJECT in it, and inherits TypedObjectListModel<Fruit>. This can only contain Fruits or Fruit-subobjects.

However, I now have an app can that run in two different states. In the second state the model should only hold Apples, no Bananas (or Fruits for that matter, which is a concrete base class).

So, I'd like to create an ApplesModel type, that should inherit FruitsModel and just change the type of T. This gets me into trouble, because I get the inheritance diamond OF DEATH:

 QObject
       |
 QAbstractListModel
       |
 ObjectListModel -------------------
       |                           |
 TypedObjectListModel<Fruit>     TypedObjectListModel<Apple>
       |                           |
 FruitsModel  -------------------ApplesModel

This is also conceptually wrong, since FruitsModel::push_back(Fruit*) is illegal in ApplesModel. However, reading/iterating over Fruits (not just Apples) should be possible.

Also, I have some functions in FruitsModel (findFruitById) that should be overriden and only return Apples in ApplesModel.

What is the preferred design pattern in solving this problem in C++?

I suspect (hope) I'm not the first trying to do something similar.

I've tried lots of ideas but I get stuck in various dead ends. You'd think virtual inheritance of ObjectListModel would solve the problem, but then I get this using QObject::findChild:

error C2635: cannot convert a 'QObject*' to a 'ApplesModel*'; conversion from a virtual base class is implied

The above can be remedied with my own implementation of findChild, using dynamic_cast instead, but there are still some dead-ends.

template<typename T>
inline T myFindChild(const QObject *parent, const QString &name = QString())
{ 
    return dynamic_cast<T>(qt_qFindChild_helper(parent, name, reinterpret_cast<T>(0)->staticMetaObject)); 
}

UPDATE

geekp had the following suggestions:

Inherit Apple from Fruit and don't bother with ApplesModel

How do I then enforce that only apples are in the FruitsModel? Also, I need to downcast every-time I fetch an apple (as fruit).

Don't inherit from FruitsModel (why would you if you are not using it's methods?)

I'm using some methods, notably so the ones for reading.

Don't inherit from TypesObjectListModel of Apple and subclass only FruitsModel.

Same drawbacks as not bothering with AppleModel.

Was it helpful?

Solution

So reading and writing operations are fundamentally different with regards to inheritance.

Going back to OOP 101, remember the parable about the square and the rectangle? It is often said that the square is a kind of rectangle, but that is only true when reading.

When writing, squares are not kinds of rectangles, but rectangles are kinds of squares!

Ie:

bool test( Rectangle* r ) {
  int old_height = r->GetHeight();
  int old_width = r->GetWidth();
  r->SetWidth(old_width+100);
  return old_height == r->GetHeight();
}

the above function returns true for all "real" rectangles, but for Squares it might not. So the contracts around SetWidth that are reasonable for Rectangle is violated for Square.

On the other hand, every interface for Rectangle that is read-only is perfectly handled by Square.

This might give you a mess like this:

struct IRectangleRead { ... };
struct ISquareRead { ... };

struct ISquareWrite: virtual ISquareRead { ... };
struct IRectangleWrite:ISquareWrite, virtual IRectangleRead { ... };

struct ConstRectangle: virtual IRectangleRead { ... };
struct ConstSquare: virtual ISquareRead, virtual IRectangleRead { ... };

struct Rectangle: ConstRectangle, IRectangleWrite { ... };
struct Square: ConstSquare, ISquareWrite { ... };

which generates a mess of an inheritance hierarchy, but one where restrictive contracts can be placed on each method, and every object that implements the method will obey them.

Now, you should note that the above becomes ridiculously easier if your objects are immutable. Then the only form of writing is via factory functions, and things become tidy.

So the concrete lesson here -- split the reading and modifying parts of your code. The common modifying part (that works on the base classes) isn't publicly exposed, because the operation is invalid in the subclass cases.

The common reading part is publicly exposed, as is the subtype reading part.

The subtype writing code forwards to the private common base class writing code.

OTHER TIPS

Multiple choices :

  • Inherit Apple from Fruit and don't bother with ApplesModel
  • Don't inherit from FruitsModel (why would you if you are not using it's methods?)
  • Don't inherit from TypesObjectListModel of Apple and subclass only FruitsModel
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top