Mat*_*att 6 c++ architecture frameworks software-design
我对这个问题的回答是"不".但我的同事不同意.
我们正在重建我们的产品,并在短期内做出许多关键决策.
在做我自己的一些工作时,我注意到我们有一些内部C++类来抽象一些POSIX API(线程,互斥,信号量和rw锁)和其他实用程序类.请注意,这些类是基本的,并且尚未从Linux移植(可移植性是重建的一个因素.)我们也使用POCO C++库.
我把它引起了我的同事的注意,并建议我们放弃我们的内部课程以支持他们的POCO等价物.我想充分利用我们已经使用的库.他们建议我们应该使用POCO实现我们的内部类,并在必要时进一步抽象出额外的POCO类,以便不依赖于任何特定的C++库(引用未来的未知数 - 如果我们想要使用不同的lib /框架,那该怎么办? QT或提升,如果我们选择的那个结果不好或开发变得不活跃,等等.)
他们也不想重构遗留代码,并且通过使用我们自己的类抽象POCO的部分,我们可以实现其他功能(经典OOP.)我可以理解这两个参数.但是,我认为,如果我们正在进行重新编码,我们应该变大,或者回家.现在是重构的时候了,它确实不应该那么糟糕,特别是考虑到我们的类和POCO(线程等)之间的相似性我不知道对于第二点说什么 - 我们应该只使用需要功能的扩展类?
我的同事也不想在整个地方乱丢POCO命名空间.我认为我们应该选择一个库/框架/工具包,并坚持下去.充分利用其功能.这不是典型的做法吗?我见过的唯一一个抽象整个框架的项目是Freeswitch(它为APR提供了自己的接口.)
一个建议是我们彼此暴露的API和潜在客户应该没有POCO,但它会出现在实现中(这是有意义的.)
我们中没有人真正拥有这些设计决策的经验,并且它在当前的产品中显示出来.从我年轻的时候就一直在这里,我有一些直觉让我来到这里,但也没有实际经验.我真的想避免解决已经解决的问题.
我认为我的问题归结为:在构建产品时,我们是否应该a)选择一个支配大部分代码的主导框架,以及b)期望该框架与产品紧密结合?这不是框架的重点吗?(框架或库是否更适合POCO?)
首先,您公开的 API 绝对应该不含 POCO、boost、qt 或不属于标准 C++ 库的任何其他类型。这是因为基础库有自己的发布周期,与您的库的发布周期不同。如果您的库的用户也使用 boost,但是不同的、不兼容的版本,他们将需要花时间来解决不兼容性问题。此规则的唯一例外是当您设计一个要作为更广泛框架的一部分发布的库时 - 例如,POCO 工具包的补充。在这种情况下,您的库的发布与整个工具包的发布相关联。
但是,在内部,您应该避免使用自己的包装器,除非您抽象出的库是真正的“商品库” 1。原因是,当您将外部库隐藏在类后面时,大多数时候您会模仿所隐藏的库的抽象级别。使用包装器的代码将按照外部库规定的抽象级别进行编程。当您将包装器后面的实现替换为不同的框架时,您很可能(1)调整新框架以适应旧框架的抽象级别,或者(2)需要更改你使用你的包装器。这两种情况都是高度可疑的:如果你这样做(1),也许你一开始就不应该切换,如果你这样做(2),那么你的包装器被证明是无用的。
我认为在两种情况下值得拥有自己的包装器:
1)您已经研究了不同系统/库上的几种不同的互斥体实现,您已经建立了一组通用的要求,它们都可以满足并且足以满足您的软件的要求。然后,您定义该抽象并实现它一次或多次,并知道您已经提前计划了灵活性。其余代码的编写仅依赖于您的抽象,而不依赖于当前实现的任何附带属性。我过去曾这样做过,尽管我可以向您展示的不是代码。
这种“最不常见的接口”的一个典型例子是在文件系统抽象中进行更改rename
,因为 Windows 无法实现对现有文件的原子重命名。因此,如果您将来可能将当前的 *nix 实现替换为无法做到这一点的实现,那么您的代码一定不能依赖于原子重命名替换。您必须从一开始就限制接口。
如果做得正确,这种接口可以大大简化未来任何类型的移植,无论是到新系统还是因为您想要更改第三方库依赖项。然而,整个框架可能太大,无法成功地做到这一点——本质上,您需要发明和编写自己的框架,这不是一项微不足道的任务,而且可以想象,这是比编写实际软件更大的任务。
2)你希望能够模拟/存根/假冒/欺骗/剽窃/无论下一个聪明的技术是什么,测试中的互斥锁,并决定如果你有自己的包装器,你会发现这比你自己更容易试图弄乱来自第三方库或内置的符号。
请注意,定义您自己的函数(称为wrap_pthread_mutex_init
等wrap_pthread_mutex_lock
)精确模拟pthread_*
函数并采用完全相同的参数,可能满足 (2) 但不满足(1)。无论如何,正确执行 (2) 可能需要的不仅仅是包装器,您通常还希望将依赖项注入到代码中。
在灵活性的标题下做额外的工作,而不真正提供灵活性,几乎是浪费时间。根据一种线程环境来实现另一种线程环境可能非常困难,甚至被证明是不可能的。如果您决定将来从 pthreads 切换到std::thread
C++,那么使用看起来与不同名称下的 pthreads API 完全相同的抽象(大约)没有任何帮助。
对于您可能做出的另一个可能的更改,在 Windows 上实现完整的 pthreads API 是可能的,但可能比仅实现您实际需要的更困难。因此,如果您移植到 Windows,您所节省的所有抽象就是搜索和替换软件其余部分中的所有调用的时间。您仍然需要 (a) 为 Windows 插入完整的 Posix 实现,或者 (b) 弄清楚您实际需要什么,然后只实现它。你的包装对真正的工作没有帮助。