Composition vs Aggregation when working with models
-
Consider this fictional scenario:
We want to develop a university management system and need to make use of Qt's model architecture. The basic data structure is as follows. A University has multiple Courses. A Course has multiple Modules.
We have 3 list views, for University, Course, and Module. Selecting a University, should display the respective Courses in the Course list, and selecting a Course should display the respective Modules in the Module list. In future, we may wish to add additional views and/or present our data differently, so our model design should be flexible.
In any case, I think it makes sense to have 3 models, subclassed from QAbstractListModel, UniversityModel, CourseModel, and ModuleModel.
Now to main the question. In a non-GUI application, I would simply have a University class that has a vector of Course, which in turn has a vector of Module. If I were to apply this composition approach in this scenario, I would re-populate the Course and Module models as items are selected, and delegate object ownership and inter-model communication to a manager class.
With only 3 list views, I imagine this approach would work just fine, while allowing us to respect the "has-a" relationship of our data. However, should we wish to use our models in additional views (with potentially different selections), we would most likely need to introduce additional models. Effectively, you would have a model for every view.
The alternative (aggregation?) I think would be to flatten our data across the 3 models, such that University contains all Universities, Course contains all Courses, and Module contains all Modules. The Course class would have a University ID var, and the Module class would have a Course ID var, which we would use to associate with our parent/children. Additionally, we would have 3 sort/filter proxy models which we would use to filter specific views.
So, which of the two approaches plays best with Qt's model architecture?
-
There won't be a module, that can be part of multiple courses, right?
So we're actually talking about a tree model, rather than two separate lists.
In that case, just inherit fromQAbstractItemModel
and implement your tree. You can visualise it using aQTreeView
. A basic approach is documented here. -
I think you are mixing two things:
The alternative/aggregation approach describes a relational data(base) model with an identity key.
A tree model is the generic way to visualize such models, preferably hiding the (random) identity keys from the user.
Of course you can fiddle around with sort/filter proxy models or list models. That will in the end be complicated and come with limitations.
I can't advise about how to do the wrong thing right.
What I can advise though is to figure out what you found difficult with tree models and tree views. Work on that, ask questions here and overcome your reluctance. IIRC we were taught at university how to do the right things right ;-) -
Would I still be able to have separate University, Course and Module objects in my tree model, and display in views other than tree view (e.g. listview, gridview, etc). I suppose part of my reluctance comes from the fact that our hierarchy only has 3 levels, and each level is of a fixed type.
-
You could make that a nice inheritance exercise, if you want:
- Create an abstract
TreeItem
class with a type as an enum and a protected constructor. - Make
University, Course, Module
inherit from it. - By checking the type, you know which class to cast the a
TreeItem
into. That way you have separate objects.
Of course you can have three list views next to each other. By clicking on a university, you refresh the view for courses and so on.
What I find hacky about this, is to handle entity relations on a UI level.
If you ever wanna display the same data e.g. in a mobile app written in QML, you have to re-implement the same logic in two pieces of UI software. That's asking for trouble. Entity relations should be handled on model level therefore. Then the same data can be consistently displayed (and if you implement it: also modified) in aQTreeView
of a widgets desktop application as well as with aTreeView
QML type. - Create an abstract
-
Thank you, I'll definitely explore what you've suggested. I think I'll start with TreeItem and work my way up from there.
Out of interest, what are your thoughts on achieving the nested data by giving each University object an instance of a CourseListModel, and each Course object an instance of ModuleListModel? We could then retrieve the required model instance via a custom role in data() and set it on the desired view. This to me seems like a simpler method, while also giving us flexibility for future expansion. What are your thoughts?
-
I am not sure if I understand that correctly. You want to build a list model basically for each node of the tree?
Sure, it can be done.
My immediate thought is: I complicated way to avoid a tree view. But maybe that's because I don't fully understand your use case. -
Thanks for the suggestion. I've found tree models difficult to work with in the past so am somewhat reluctant to use them here. Any thoughts on the second approach I suggested?
-
How so?
Traversing a tree to get a Module in our non-GUI related code is more difficult than just doing universities[0].courses[0].module[0]. I have wondered though whether I could keep my data separate from QModelIndex, where a simplified "TreeItem" contains only the minimum data needed to locate the required object elsewhere. Or does that seem hacky to you?
-
How so?
Traversing a tree to get a Module in our non-GUI related code is more difficult than just doing universities[0].courses[0].module[0]. I have wondered though whether I could keep my data separate from QModelIndex, where a simplified "TreeItem" contains only the minimum data needed to locate the required object elsewhere. Or does that seem hacky to you?
@ECEC said in Composition vs Aggregation when working with models:
Traversing a tree to get a Module in our non-GUI related code is more difficult than just doing universities[0].courses[0].module[0].
Well, why? To use a tree model you need to be able to "quickly" locate a given item. The way you describe your data you ought be able to do this. If not then why not, and it might be indicative of why you are having problems with a tree.
It ought not really matter whether you build your data structures around native Qt tree items or whether you pick your own data structure so long as you can map access between yours and Qt models, which you should be able to do.
I could keep my data separate from QModelIndex, where a simplified "TreeItem" contains only the minimum data needed to locate the required object elsewhere
Yes you can do that. It should not matter so long as there is a "natural" (i.e. quick) mapping both ways between your data structures and Qt's model requirements.
-
I think I've been really overthinking things and worrying about abusing the Qt model architecture.
As we have a predictable structure, where each level of the tree has a known type, I suppose I wouldn't even need to subclass University, Course and Module from a generic TreeItem. I could simply store the pointer in internalPointer and then static_cast back to the required type depending on what level we are at. -
I think I've been really overthinking things and worrying about abusing the Qt model architecture.
As we have a predictable structure, where each level of the tree has a known type, I suppose I wouldn't even need to subclass University, Course and Module from a generic TreeItem. I could simply store the pointer in internalPointer and then static_cast back to the required type depending on what level we are at.@ECEC said in Composition vs Aggregation when working with models:
I suppose I wouldn't even need to subclass University, Course and Module from a generic TreeItem. I could simply store the pointer in internalPointer and then static_cast back to the required type depending on what level we are at.
Exactly, if that is what suits best.