【问题标题】:Is it bad practice to use co-dependent classes?使用相互依赖的类是不好的做法吗?
【发布时间】:2011-05-04 21:09:55
【问题描述】:

如果我有一个 Container 类,其中包含一堆 Component 对象(类似于 Java 的 UI 框架),那么在每个 Component 中跟踪 Container 父级会是一种糟糕的风格/做法吗?

这最终会导致一些奇怪的编译器问题,因为Component 包含Container 的头文件,反之亦然。即使修复了标头保护,我最终还是不得不在 Container 标头之上声明原型 class Component;Component 也是如此。

看起来你必须经历相当多的麻烦才能在两个类之间实现双向交互。为共同依赖“解决”的问题寻找另一种解决方案是可取的,还是这种复杂的实现是 C++ 预期的,我应该把它吸干?

编辑:也许一些上下文/推理会有所帮助。我使用这种共同依赖的原因是因为我需要在调用孩子的析构函数时通知父母(因此可以将其从孩子列表中删除),并且我还需要绘制孩子的位置是相对于父母的。

谢谢,
詹格勒

【问题讨论】:

  • 我去掉了C标签,因为这确实是一个C++问题。
  • 对我来说 GUI 标签是否合适也不是很明显 - 会让 Jengerer 重新考虑。
  • 好吧,ComponentContainer 类用于我正在使用 C++ 开发的 GUI。不过,我同意这并不是那么必要。
  • 如果一个类只包含一个引用或一个指针,那么你不需要完整的定义,因此你可以转发声明它而不是包含头文件。

标签: c++ oop header dependencies


【解决方案1】:

让组件保留指向其父级的指针已被广泛使用,我不认为这是不好的做法。它可以用于各种各样的事情,从处理小部件树到处理丢失或泄漏的组件。

要解决编译器的问题,您需要将包含行放在标头保护中,或者有一个包含两个类的前向声明的第三个文件(在适当的命名空间中)。

我已经在一些大型 OO 库中看到了后者,然后它通常包含在项目中的几乎所有其他文件中。虽然这可能会导致一些命名空间污染,但它有一些好处(不需要包含完整的类定义或另一个类的依赖项只是为了获得指向它的指针)。

Widget.hpp:

#include "Library.hpp"

namespace MyLibrary
{
    class Widget
    {
    private:
        Window * parent;
    };
}

Window.hpp:

#include "Library.hpp"

namespace MyLibrary
{
    class Window
    {
    private:
        std::vector<Widget*> widgets;
    };
}

库.hpp:

namespace MyLibrary
{
    class Window;
    class Widget;
}

【讨论】:

  • 好建议。我可能会接受 Derek 的建议,因为它通常看起来更容易实现,同时仍然可以完成相同的工作。不过,感谢您的意见!
【解决方案2】:

其实我认为这是一个设计问题。你真的应该把设计看成一个整体,看看你能不能把它设计得更好。问问自己为什么有依赖类需要访问容器。也许容器提供了它不应该提供的功能。能否以其他方式提供此功能?

【讨论】:

  • 我尝试使用这种设计解决的主要问题是:a) 当Component 被销毁时,我想通知父级它应该从子级列表中删除。 b) 当Component 被绘制到屏幕上时,我希望它相对于父级的位置绘制自身。在不使用这些依赖类的情况下如何解决这些问题?我真的想不出比这个更简洁的方法了。
  • 嗯,首先 - 破坏组件的代码在哪里?也许这应该是容器的一个功能,以便它既可以删除组件,也可以将其从本地列表中删除。与绘图功能相同。容器绘制函数可以指示每个组件进行绘制,给它一个开始的 x/y 位置。换句话说,使用删除和绘制功能,为什么不将容器作为主要入口点。另一种选择是考虑使用观察者模式,其中对容器的引用是通过接口实现的。
  • 好主意,我想我最终可能会接受这个建议。我实际上考虑在外部设置孩子的位置,但我犹豫了,因为我不喜欢每次都计算它,但我意识到这本质上已经是我的程序所做的,只是在孩子中计算位置。
【解决方案3】:

通常最好不要有这样的循环依赖:它们对代码来说有点痛苦,对代码的逻辑推理感到沮丧,可测试性等等。

无论如何,它们都可以很容易地避免——你可以在组件中保留一些函子,然后将它们指向适合正在处理的事件的容器操作,或者从抽象接口派生容器,这样组件就可以有一个指向它和容器由此派生。或者组件可以通过(智能)指针存储,派生类型实现为容器定制的虚拟事件处理程序。还有很多其他的可能性,例如如果您想避免运行时开销,请考虑 CRTP 或模板策略类。

【讨论】:

    猜你喜欢
    • 2018-07-01
    • 1970-01-01
    • 2015-11-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-11-23
    • 2011-09-18
    • 1970-01-01
    相关资源
    最近更新 更多