保持模块独立,同时仍然相互使用
我的 C++ 应用程序的很大一部分使用类来描述数据模型,例如 ClassType(实际上模拟普通 C++ 中的反射)。
我想向我的应用程序添加一个新模块,并且它需要使用这些 ClassType,但我不喜欢在 ClassType 上引入新模块的依赖项。
到目前为止,我有以下替代方案:
- 不使其独立并引入对 ClassType 的依赖关系,这有在我的应用程序中创建更多“意大利面条”依赖关系的风险(这是我最不喜欢的解决方案)
- 引入一个新类,例如 IType,并让我的模块仅依赖于 IType。 ClassType 应该继承自 IType。
- 使用字符串作为标识方法,并在需要时强制新模块的用户将 ClassType 转换为字符串,反之亦然。
- 使用 GUID(甚至简单的整数)作为标识,还需要在 GUID 和 ClassType 之间进行转换
在应用程序中解耦模块时,您应该尝试走多远?
- 只引入一个接口,让其他所有模块都依赖这个接口? (就像上面描述的 IType 一样)
- 甚至通过使用其他标识(例如字符串或 GUID)来进一步解耦它?
恐怕解耦得太远,代码会变得更不稳定,更难调试。我在 Qt 中见过这样一个例子:信号和槽使用字符串链接,如果输入错误,该功能将不起作用,但它仍然可以编译。
您应该将模块解耦到什么程度?
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?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
99% 的情况下,如果您的设计基于反射,那么您的设计就会出现重大问题。
一般来说,类似的设计
是一个糟糕的设计,因为它忽略了多态性。如果您这样做,那么项目
x
就违反了里氏替换原则。另外,考虑到 C++ 已经有了 RTTI,我不明白你为什么要重新发明轮子。这就是
typeof
和dynamic_cast
的用途。99% of the time, if your design is based on reflection, then you have major issues with the design.
Generally speaking, something like
is a poor design because it neglects polymorphism. If you're doing this, then the item
x
is in violation of the Liskov Substitution Principle.Also, given that C++ already has RTTI, I don't see why you'd reinvent the wheel. That's what
typeof
anddynamic_cast
are for.我将不再考虑你的反思,而只关注依赖的想法。
解耦合理的解耦。耦合意味着如果一件事发生变化,另一件事也必须发生变化。因此,您的 NewCode 使用的是 ClassType,如果它的某些方面发生变化,那么您肯定必须更改 NewCode - 它无法完全解耦。您希望与以下哪一项脱钩?
在我看来,前两个是合理的耦合。但肯定的是,实现更改不应要求 NewCode 更改。所以编写接口代码。我们尝试保持接口固定,我们倾向于扩展它们而不是改变它们,如果可能的话保持它们向后兼容。有时我们使用名称/值对来尝试使接口可扩展,然后遇到您提到的拼写错误。这是灵活性和“类型安全”之间的权衡。
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".
这是一个哲学问题;这取决于模块的类型和权衡。我想我个人在不同的时间做过所有这些,除了 GUID 到类型的映射,在我看来,它比字符串到类型的映射没有任何优势,而且至少字符串是可读的。
我想说,考虑到预期的外部使用和代码组织,您需要考虑特定模块需要什么级别的解耦,然后从那里开始。据我所知,您已经掌握了所有概念方法,并且它们在特定情况下都很有用。
无论如何,这就是我的意见。
It's a philosophical question; it depends on the type of module, and the trade-offs. I think I have personally done all of them at various times, except for the GUID to type mapping, which doesn't have any advantages over the string to type mapping in my opinion, and at least strings are readable.
I would say you need to look at what level of decoupling is required for the particular module, given the expected external usage and code organization, and go from there. You've hit all the conceptual methods as far as I know, and they are each useful in particular situations.
That's my opinion, anyway.