A big part of my C++ application uses classes to describe the data model, e.g. something like ClassType (which actually emulates reflection in plain C++).
I want to add a new module to my application and it needs to make use of these ClassType’s, but I prefer not to introduce dependencies from my new module on ClassType.
So far I have the following alternatives:
- Not making it independent and introduce a dependency on ClassType, with the risk of creating more ‘spaghetti’-dependencies in my application (this is my least-preferred solution)
- Introduce a new class, e.g. IType, and letting my module only depend on IType. ClassType should then inherit from IType.
- Use strings as identification method, and forcing the users of the new module to convert the ClassType to a string or vice versa where needed.
- Use GUID’s (or even simple integers) as identification, also requiring conversions between GUID’s and ClassType’s
How far should you try to go when decoupling modules in an application?
- just introduce an interface and let all the other modules rely on the interface? (like in IType describe above)
- even decouple it further by using other identifications like strings or GUID’s?
I afraid that by decoupling it too far, the code becomes more unstable and more difficult to debug. I’ve seen one such example in Qt: signals and slots are linked using strings and if you make a typing mistake, the functionality doesn’t work, but it still compiles.
How far should you keep your modules decoupled?
I’ll steer away from thinkng about your reflection, and just look at the dependency ideas.
Decouple what it’s reasonable to decouple. Coupling implies that if one thing changes so must another. So your NewCode is using ClassType, if some aspects of it change then yuou surely must change NewCode – it can’t be completely decoupled. Which of the following do you want to decouple from?
To my eyes the first two are reasonable coupling. But surely an implementation change should not require NewCode to change. So code to Interfaces. We try to keep Interfaces fixed, we tend to extend them rather than change them, keeping them back-compatible if at all possible. Sometimes we use name/value pairs to try to make the interface extensible, and then hit the typo kind of errors you allude to. It’s a trade-off between flexibility and “type-safety”.