【问题标题】:When classes want to couple当班级想要耦合时
【发布时间】:2010-09-06 16:08:38
【问题描述】:

我遇到了 2 个曾经很好分开的班级的问题,但现在他们想要结合。

没有过多讨论问题的细节,这里是:

我曾经有一个包含 3 个空间位置顶点的三角形类。

class Triangle
{
    Vertex a,b,c ; // vertices a, b and c
} ;

程序中有很多 Triangle 实例,因此每个实例都保留了自己的顶点副本。 getArea()getCentroid() 等成员函数写在 Triangle 类中,由于每个 Triangle 实例都有顶点 a、b 和 c 的副本,因此查找区域或质心不依赖于其他类。应该是这样!

然后出于其他原因,我想转移到顶点数组/索引缓冲区样式表示。这意味着所有顶点都存储在位于Scene 对象中的单个数组中,并且每个Triangle 仅保留对Scene 中顶点的引用,而不是顶点本身的副本。起初,我尝试切换出指针:

class Scene
{
    std::vector<Vertex> masterVertexList ;
} ;

class Triangle
{
    Vertex *a,*b,*c ; // vertices a, b and c are pointers
    // into the Scene object's master vertex list
} ;

(如果您想知道好处,我这样做的原因主要是与共享顶点的三角形有关。如果 *a 移动,则使用该顶点的所有三角形都会自动更新)。

这将是一个非常好的解决方案!但它不能可靠地工作,because std::vector invalidates pointers,我在 Scene 类中使用 std::vector 作为主顶点列表。

所以我不得不使用整数:

class Triangle
{
    int a,b,c ; // integer index values
    // into the Scene object's master vertex list
} ;

但是现在我遇到了这个新的耦合问题:要找到它自己的区域或质心,Triangle 类需要访问 class Scene,而之前它没有。看起来我已经 fsck`d 了一些东西,但不是真的。

WWYD?

【问题讨论】:

  • 这种语言如何不可知?
  • 这些课程让我想起了 George Costanza:“我已经耦合了!我已经耦合了!”
  • 看起来“场景”应该由“三角形”组成。您是否考虑过(并拒绝)这种可能性?

标签: c++ class coupling


【解决方案1】:

为什么不让Scene 中的vector 也只存储指针?

std::vector<Vertex *> masterVertexList;

这样,Triangle 可以继续使用Vertex * 的,您所要做的就是确保在Scene 的析构函数中删除了指针。

【讨论】:

  • 这是一个非常好的建议!我将它们保留为普通 Vertex 而不是 Vertex* 的原因是我希望能够在绘制时将 masterVertexList 作为普通数组传递给 GPU,(使用 &amp;masterVertexList[0]
  • 或者更确切地说,vector&lt;shared_ptr&lt;Vertex&gt; &gt;
  • 因为顶点通常由(相邻)三角形共享。
  • @tenfour:shared_ptr 在添加更多项目时移动项目,shared_ptr 不会造成麻烦吗?
  • @bobobobo:我猜你会被Triangle 的指针指向Scene 所困。
【解决方案2】:

您可以在其构造函数中将向量传递给三角形,以便它可以保留对向量的引用。然后它不需要访问或知道场景。

typedef std::vector<Vertex> VertexContainer;

class Scene
{
    VertexContainer  masterVertexList ;  
} ;  

class Triangle  
{  
    // A references to the vertices contained in Scene.
    // A triangle no longer needs to know anything about a scene
    VertexContainer&   vertexListRef;

    // index into vertexListRef of the triangles points.
    VertexContainer::size_type  a;
    VertexContainer::size_type  b;
    VertexContainer::size_type  c;  

    public:
        Triangle(VertexContainer&           masterVertexList,
                 VertexContainer::size_type x,
                 VertexContainer::size_type y,
                 VertexContainer::size_type z)
            :vertexListRef(masterVertexList)
            ,a(x),b(y),c(z)
        {}
};

【讨论】:

  • 这就是这样做的方法。对独立于场景的向量集合的概念进行建模。现在三角形只耦合到 VertexContainer,而不是 Scene。 VertexContainer 是Triangle 需要知道的Scene 的唯一 方面,因此将VertexContainer 和Scene 分开,让Triangle 只知道VertexContainer。
  • ...和 ​​int 应该是 VertexContainer::size_type
  • 如果所有三角形共享同一个顶点列表,你应该将引用设为静态。
  • @乔治爱迪生:这听起来不合理。这意味着所有场景中的所有三角形共享相同的顶点容器。我认为这需要坚持。只是同一个Scene 中的所有三角形共享同一个 VertexContainer。
【解决方案3】:

在我看来,您的三角形确实取决于您的场景(因为它的顶点都是该特定场景的所有成员),所以让对象这样做并不丢人。事实上,我可能会给 Triangle 一个必须的 Scene* 成员。

【讨论】:

  • 没有。三角形只依赖于场景中的顶点集合,而不是场景本身。单独对集合建模,您可以打破 Triange 和 Scene 之间的明显耦合。请参阅 Martin York 的回答。
【解决方案4】:

从非耦合到耦合的变化是您决定尽可能共享顶点的自然结果。以前,每个三角形都“拥有”它的顶点,而场景(大概)拥有一堆或三角形。

允许三角形共享顶点改变了基本模型——当/如果一个顶点可能在两个或多个三角形之间共享时,没有一个三角形可以再拥有该顶点。虽然可能(例如,使用类似于 shared_ptr 的东西)拥有分布式共享所有权方案,但您现在所做的可能更直接:每个顶点仍然有一个所有者,但是所有者现在是场景而不是单个三角形。

由于三角形现在只是对“拥有”集合中的一些顶点进行分组而不是拥有顶点本身的一种便捷方式,因此三角形和拥有其顶点的集合之间的耦合更紧密也就不足为奇了。如果您非常关心它,您仍然可以隐藏共享所有权,以至少保留您之前松散耦合的外观。

一般的想法相当简单:不是每个三角形都知道包含三角形顶点的场景,而是创建一个结合了场景 ID 和顶点索引的顶点代理类,以便三角形可以操纵顶点代理像以前一样的对象将具有顶点对象。您并没有完全消除更紧密的耦合,而是将更紧密耦合的“知识”隔离到单个类中,该类负责保持松散耦合的外观。

这样做的明显缺点是顶点代理对象可能会存储大量冗余数据。例如,任何特定三角形中的所有顶点代理都清楚地表示同一场景中的顶点。如果您为每个顶点代理显式存储场景 ID,您将存储三个场景 ID 副本,而不是之前的一个副本。有时这是值得的——其他时候则不然。如果这是一个真正的问题,您可以尝试想出一种方法来避免显式存储场景 ID,但这可能会涉及一些与语言无关(甚至接近)的技巧。

【讨论】:

    【解决方案5】:

    如果您只是在顶点列表的末尾添加或删除,请改用deque

    【讨论】:

    • deque 不能保证其所有元素都位于连续的存储位置。乔治爱迪生的答案中似乎已经添加了这个连续的要求。
    【解决方案6】:

    我不认为这太糟糕了。 Triangle 失去了一些通用性,成为了 Scene 的外围类,但如果它不用作外部接口(与内部缓冲区的那种链接表明不是),那只是自然演变。

    我的解决方案在本质上与您的解决方案相似,但加了更多糖。

    struct Triangle
    {
        Triangle( ... ) { ... }
        Vertex *a(),*b(),*c() ; // trivia: this is valid syntax! Getters adjust…
    private:
        size_t ax, bx, cx; // … offsets…
        Scene *client; // … into the Scene object's master vertex list.
    } ;
    

    这样,你不必重新组织内存中的东西,而适应旧代码只需要添加()-&gt;a.a等,可以通过搜索和替换来完成,并且无论如何都改进了 OO 风格。

    或者,取消构造函数和 private 并使其成为 POD。

    【讨论】:

    • 对……不想给人留下我实际上会这样做的印象……只是在编辑 OP 的代码时偶然发现它并且懒得填写定义。
    【解决方案7】:

    假设您只有一个Scene,您可以将其设为单例对象并通过静态方法访问顶点列表。

    如果您有多个Scene 对象,那么每个Triangle 恰好属于一个Scene - 它应该“知道”它属于哪个场景。因此,您应该使用 Scene 引用初始化每个 Triangle,并将其存储为类成员。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-06-12
      • 1970-01-01
      • 1970-01-01
      • 2019-04-26
      • 1970-01-01
      • 2016-06-06
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多