Signals vs method override when implementing component behaviours
-
Hi,
this is meant to be a discussion about how do you think is best to realize specialized components. Imagine you are developing a widget, that contains other widgets as well as actions.When a certain action is triggered, the widget must "invoke" a defined behaviour, that can change depending on the widget(s) internal status.
I see two possible solutions to the problem:the widget computes an internal status summary and emits a signal with such status summary (could be just an int, an sql record, or something else
the widget computes its internal status and calls a virtual method (on itself or another object, it does not matter) with such status
The only difference I can see between the two above is that using signals it is possible to connect more than one "listener" to the event, but it must be noted that usually a behaviour is implemented in a single space, so this does not sound soo much a limitation. Nevertheless, the adoption of signals could make the widget a complete class, without requiring you to subclass it and have to override methods. Again, this does not sound too much bad for complex and quite specialized components.
At the end it seems to me only a problem of how many signals and slots are involved in the process. For instance, considering the triggering of an action:a slot is invoked to manage the action
the slot computes the internal state and
invokes a method OR emits a signal
the final method/slot is invoked
Comments?
-
From an object oriented point of view, signals are the way to go, especially in case you want to just use the widget. This way you do not need to know the class' internals in order to do your final work. The number of signals and slots connections should be roughly the same in both cases, as you need to connect to the internal signals too on the subclassing approach (your virtual slots must be called!).
-
Thanks.
An advantage I can see in overriding methods, that may be possible even with signals but I don't know why, is to check the return value of the method so that a specific behaviour can be encapsulated in the superclass and therefore be the same for all the widgets.For instance, imagine in the superclass I've got:
@ if( ! doDelete() )
QMessageBox::critical( this, tr("Deletion Error"),
tr("An error occured while deleting the element") );@
being doDelete a virtual method that returns a bool. If now I override such method, the children has to only return the right value to triggered the dialog box in the case of failure. With signals I should have written:
@
emit doDeleteSignal();
@and the check of the deletion, and therefore the behaviour, should have been enclosed in the slot, and therefore it could have been implemented in different ways.
Please advice if something like the above can be done with signals and slots. -
You could just emit a signal if an error occurs.
I think that you should recognize that there is a place for both signals and reimplementing virtual functions. They are not exclusive, they are orthagonal. Use inheritance and virtual methods only if you really have an is-an relation between your classes. That is: everywhere where a class of type A is expected, you can substitute class Ai that inherites from A, and it is completely valid to treat Ai as an object of class A.
Note that although a standard signal-slot connection does not support return values, there are some tricks you can use. One trick I sometimes use (but try to avoid it if possible!) is to pass a pointer to a value as a parameter of the signal, and let the receivers write to that. Please note that this is not thread save, and that if you have multiple slots connected to your signal, they will each have the opportunity to write to that value. However, sometimes, that is ok, like in the case if where the emitter of the signal likes to know if all the listeners have succeeded with their requested operation.
Another trick to work with return values while still using slots, is to use QMetaObject::invokeMethod instead of relying on the connect() mechanism. Downsides are obvious: you will need to care of the administrative burden of making sure you call all listeners and keep track of them. Basically, you are making an implementation of the observer patttern, with the added benefit of not being stuck to having to use a only a specific method for your observer (the class is specific: QObject). Not all that attractive, I think, but it can work and might be useful if the added benefits are required for your situation.
There are of course even more possible solutions. My rule of thumb in selecting a solution is to prefer the solution that results in the weakest relationships between the involved classes, and that promotes the most opportunities for code-reuse. Inheritance is a very strong relationship, while signals and slots are relatively weak (but certainly not the lowest on the scale).
-
Thanks.
I agree that is indeed necessary to recognize when to use the right facility, and that inheritance is a quite strong mechanism. In my case the temptation is high: I've to develop a set of widgets that will act exactly the same and change only in their initialization data, so having to provide signals to simply re-implement the same logic in all of them seems to me quite odd. However I was curious to know how you develop in such situations, and of course, it is often quite simply to convert a slot connection to a signal (refactoring).
I am aware of the tricks to pass back a signal-processed value, but as you state they are not very portable and, most notably, not thread safe. I guess such implementations should be avoided for normal application and should be used only when developing a much complex framework.