Background:
- I have my class called
ObjectListModelwhich inheritsQAbstractListModeland contains aQObjectList. The objects are rows and their properties are columns (set using aQMetaObject), 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 overridingpush_backet.al. with and defining newiteratortypes that dostatic_castto 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.
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:
the above function returns
truefor all “real” rectangles, but forSquaresit might not. So the contracts aroundSetWidththat are reasonable forRectangleis violated forSquare.On the other hand, every interface for
Rectanglethat is read-only is perfectly handled bySquare.This might give you a mess like this:
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.