I've recently been working on a customerproject that makes extensive use of a signals/slots like mechanism. Their approach is to directly connect signals from a larger framework to private methods of their businessobjects. Now, my role has mostly to do with unittesting, and their approach makes testing fantastically hard, since most of the stimulation received by the businessobjects is generated by the framework by means of the signals, which leaves my unable to touch the actual logic of the businessobjects (their public interface is super anemic, often only consisting of a C'tor/D'tor), without having a complete running instance of the framework between the test and the code under test, i.e. I basically end up with an integration test.
I feel slots used that way should actually be part of a class' public interface, since they are called by external code, but I'm having a hard time finding better better arguments for this "feeling" other than "It makes testing easier". If the design were mine I'd mandate to only ever use public methods for callbacks/slots, but since that's customercode I can't make that decision, but need to convince the customer's engineers to do that, especially if they argue, that what they're doing, is actually "encapsulation".
So far my list of points are:
- Using private methods this way makes the interface of the class "lie", i.e.: In the headerfile the method is marked as private, while it is actually sort of public for a specific other object.
- From a practical standpoint it makes unit testing hard or even impossible.
My suggestions would be:
- Either don't use that signals/slots like mechanism altogether (which is probably not feasible)
- Or: Only use public methods of a businessobject as slot, ideally coupled with separating the "wiring" of the signals from their definition, so that a testenv can wire up the signals differently or even call the respective methods directly.
Am I missing something? Am I completely wrong with the opinion, that slots are actually part of the public interface and should therefore not be hidden away?
Edit: To clarify a bit more. The class hierarchy I have to deal with is this (simplified!)
ObjectContainingSignals <--- Inherit --- BusinessObject
And the border between the framework and the actual application is basically the inheritance here, so what happens is, that the applicationcode usually does something like:
this.connect(this->privateHandlerFunc, SomeSignalName)
@Sebastian Redls suggestion would be great in cases where instead of inheritance we have something like this:
ObjectContainingSignals <--- use --- BusinessObject
and we have the option inject ObjectContainingSignals into the BO (ideally ObjectContainingSignals would be hidden behind an interface).