Interacting with model data directly or via the model's interface.
-
I'll use the following scenario to give context to my question:
Suppose I have a subclass of QAbstractListModel, JourneyListModel, which is used by a QML list view to display status data contained in the model's QList of Journey objects. The Journey class has many methods, loadJourney(), clearJourney(), setDestination(), setRoute(), etc, with many of those methods resulting in data changes we'd wish to display.
Supposedly, best practice entails interacting with the the model's objects via the model's interface and not exposing non-const references to the underlying data outside of the model, but this approach doesn't sit right with me.
For every method in Journey, there would need to be a corresponding method in the model's interface. As Journey's functionality grows (with potentially many methods which don't cause displayable data changes), it becomes increasingly difficult to maintain this. Furthermore, I fear that "protecting" the Journey objects behind the model may restrict, or complicate, future development. Journey's methods need to be called from many directions: the UI, incoming network data managed by another class, whatever...
Here I propose what I consider to be a cleaner alternative:
JourneyListModel does not have ownership of the Journey objects, but rather holds a list of pointers to the objects which are held in QList<Journey> in another class, JourneyManager. JourneyManager is responsible for creating and deleting Journey objects. Both operations emit signals, passing a pointer to the newly created/deleted Journey object. A slot in the model connects to these signals and calls begininsertrows(), etc.
JourneyManager can provide other modules with a non-const pointer to a requested Journey object that they can interact with directly. As for notifying the model that data has changed, well... Have Journey emit a journeyUpdated() signal where needed, and connect a slot in the model to the signal when the object is created. This way users of Journey do not need to concern themselves with updating the model or emitting dataChanged().
Is this approach sensible, or should I follow "good practice" and do it the prescribed way?
-
I'll use the following scenario to give context to my question:
Suppose I have a subclass of QAbstractListModel, JourneyListModel, which is used by a QML list view to display status data contained in the model's QList of Journey objects. The Journey class has many methods, loadJourney(), clearJourney(), setDestination(), setRoute(), etc, with many of those methods resulting in data changes we'd wish to display.
Supposedly, best practice entails interacting with the the model's objects via the model's interface and not exposing non-const references to the underlying data outside of the model, but this approach doesn't sit right with me.
For every method in Journey, there would need to be a corresponding method in the model's interface. As Journey's functionality grows (with potentially many methods which don't cause displayable data changes), it becomes increasingly difficult to maintain this. Furthermore, I fear that "protecting" the Journey objects behind the model may restrict, or complicate, future development. Journey's methods need to be called from many directions: the UI, incoming network data managed by another class, whatever...
Here I propose what I consider to be a cleaner alternative:
JourneyListModel does not have ownership of the Journey objects, but rather holds a list of pointers to the objects which are held in QList<Journey> in another class, JourneyManager. JourneyManager is responsible for creating and deleting Journey objects. Both operations emit signals, passing a pointer to the newly created/deleted Journey object. A slot in the model connects to these signals and calls begininsertrows(), etc.
JourneyManager can provide other modules with a non-const pointer to a requested Journey object that they can interact with directly. As for notifying the model that data has changed, well... Have Journey emit a journeyUpdated() signal where needed, and connect a slot in the model to the signal when the object is created. This way users of Journey do not need to concern themselves with updating the model or emitting dataChanged().
Is this approach sensible, or should I follow "good practice" and do it the prescribed way?
@ECEC said in Interacting with model data directly or via the model's interface.:
Supposedly, best practice entails interacting with the the model's objects via the model's interface and not exposing non-const references to the underlying data outside of the model, but this approach doesn't sit right with me.
Do you have a link to an example of this advice? The internet is full of all sorts of suggestions. Maybe with a little context, the author's POV will make more sense.
-
@ECEC said in Interacting with model data directly or via the model's interface.:
Supposedly, best practice entails interacting with the the model's objects via the model's interface and not exposing non-const references to the underlying data outside of the model, but this approach doesn't sit right with me.
Do you have a link to an example of this advice? The internet is full of all sorts of suggestions. Maybe with a little context, the author's POV will make more sense.
-
Not to hand, but it's definitely an opinion I've seen multiple times. I'm reassured though by your comment that other methods can also be acceptable. What did you think of my suggested approach?
Your approach has its merits but it also implies that the class of your object must derive from QObject which has an overhead to take into account.
For changing the data of your object, one classic way to do it is through custom roles. That way you don't need to add new APIs to your model. In the the absolute, the model should be a thin wrapper around your data structure providing a simple API to help manage the objects.
-
Your approach has its merits but it also implies that the class of your object must derive from QObject which has an overhead to take into account.
For changing the data of your object, one classic way to do it is through custom roles. That way you don't need to add new APIs to your model. In the the absolute, the model should be a thin wrapper around your data structure providing a simple API to help manage the objects.
I certainly intend to use custom roles to display the data to the list view (or grid view), but the status data isn't changed via the view, but from other ares of the system, most likely data coming in over the network. So, there still needs to be some other mechanism to set this and update the model. For added info, each object is updated 10/sec with data from the network, and there are a maximum of 255 objects. Would inheriting from Q_Object be too much of an overhead in this case?
-
I certainly intend to use custom roles to display the data to the list view (or grid view), but the status data isn't changed via the view, but from other ares of the system, most likely data coming in over the network. So, there still needs to be some other mechanism to set this and update the model. For added info, each object is updated 10/sec with data from the network, and there are a maximum of 255 objects. Would inheriting from Q_Object be too much of an overhead in this case?
With that number it's fine.
You know that you can use custom roles for both reading and setting data ?
-
With that number it's fine.
You know that you can use custom roles for both reading and setting data ?
Do you mean via setData? If so, it's not as nice an API as I could have if I had dedicated methods on the objects for setting specific variables, performing calculations and then setting, etc. Would you agree?
I appreciate it's hard to quantify with the limited info I've given you, but very approximately, how many objects inheriting Q_OBJECT would it take for you to be concerned about overhead?