什么是表示两个类之间多对多关系的好方法?


10

假设我有两个对象类型,A和B。它们之间的关系是多对多的,但是它们都不是另一个的所有者。

A和B实例都需要知道连接。这不只是一种方法。

因此,我们可以这样做:

class A
{
    ...

    private: std::vector<B *> Bs;
}

class B
{
    private: std::vector<A *> As;
}

我的问题是:我将函数放在哪里创建和销毁连接?

应该是A :: Attach(B),然后更新A :: Bs和B :: As向量吗?

或者应该是B :: Attach(A),这似乎同样合理。

这些都不对。如果我停止使用代码,并在一周后返回,我确定我将无法回忆起是否应该执行A.Attach(B)或B.Attach(A)。

也许应该是这样的函数:

CreateConnection(A, B);

但是创建全局函数似乎也不可取,因为它是专门用于仅使用类A和B的函数。

另一个问题:如果我经常遇到这个问题/要求,我能以某种方式为它解决问题吗?也许可以从共享此类关系的类中派生或使用的TwoWayConnection类?

有什么好的方法可以处理这种情况...我知道如何很好地处理一对多的“ C拥有D”情况,但是这比较棘手。

编辑:只是为了使其更明确,这个问题不涉及所有权问题。A和B都由其他某个对象Z拥有,并且Z负责所有所有权问题。我只对如何创建/删除A和B之间的多对多链接感兴趣。


我将创建一个Z :: Attach(A,B),因此,如果Z拥有“感觉不错”这两种对象,则他可以创建连接。在我的示例中(不好),Z是A和B的存储库。没有一个实用的示例,我看不到其他方法。
马查多,2012年

1
更准确地说,A和B可能拥有不同的所有者。也许A由Z拥有,而B由X拥有,X又由Z拥有。如您所见,尝试管理其他位置的链接时,它变得非常复杂。A和B需要能够快速访问与其链接的对的列表。
德米特里(Dmitri Shuralyov)2012年

如果您对我的情况下的A和B的真实名称感到好奇,它们就是PointerGestureRecognizer。指针由InputManager类拥有和管理。GestureRecognizers由Widget实例拥有,而Widget实例又由Screen实例拥有,而Screen实例则由App实例拥有。指针被分配给GestureRecognizer,以便它们可以向其输入原始输入数据,但是GestureRecognizers需要知道当前与它们关联的指针有多少(以区分1个手指还是2个手指手势等)。
德米特里(Dmitri Shuralyov)2012年

现在,我考虑了更多,似乎我只是在尝试基于帧(而不是基于指针)的手势识别。因此,我最好一次将事件传递给手势,而不是一次将指针传递给手势。嗯
德米特里(Dmitri Shuralyov)2012年

Answers:


2

一种方法是向每个类添加一个公共Attach()方法以及一个受保护的AttachWithoutReciprocating()方法。制作AB共同的朋友,使他们的Attach()方法可以调用其他的AttachWithoutReciprocating()

A::Attach(B &b) {
    Bs.push_back(&b);
    b.AttachWithoutReciprocating(*this);
}

A::AttachWithoutReciprocating(B &b) {
    Bs.push_back(&b);
}

如果您为实现了类似的方法B,则无需记住要调用哪个类Attach()

我敢肯定,您可以将这种行为包装在一个MutuallyAttachable既可以继承A又可以B继承的类中,从而避免重复自己,并在审判日获得加分。但是,即使是简单的两地实施方法也可以完成工作。


1
这听起来完全像是我脑子里想的解决方案(即将Attach()放在A和B中)。我也非常喜欢DRY解决方案(我真的不喜欢重复代码),它具有一个MutuallyAttachable类,您可以在需要这种行为时从中派生该类(我经常看到)。看到其他人提供的相同解决方案使我对此更加自信,谢谢!
德米特里(Dmitri Shuralyov)2012年

接受这一点是因为IMO是迄今为止提供的最佳解决方案。谢谢!我现在要实现MutuallyAttachable类,并在遇到任何无法预料的问题时在这里报告(由于现在继承的多重性(我还没有太多经验),因此可能还会出现一些困难)。
德米特里(Dmitri Shuralyov)2012年

一切顺利进行。但是,根据我对原始问题的最后评论,我开始意识到我不需要此功能。我不会跟踪这些2通连接,而是将每对作为参数传递给需要它的函数。但是,这在以后可能仍然派上用场。
德米特里(Dmitri Shuralyov)

MutuallyAttachable如果有人想重用它,这是我为该任务编写的类:goo.gl/VY9RBPastebin)编辑:可以通过将其设为模板类来改进此代码。
德米特里(Dmitri Shuralyov)

1
我已将MutuallyAttachable<T, U>课程模板放在Gist。 gist.github.com/3308058
Dmitri Shuralyov

5

关系本身是否可能具有其他属性?

如果是这样,那么它应该是一个单独的类。

如果没有,那么任何往复式列表管理机制就足够了。


如果是单独的类,那么A将如何知道其B的链接实例,而B如何知道其A的链接实例?那时,A和B都需要与C建立一对多的关系,不是吗?
德米特里(Dmitri Shuralyov)

1
A和B都需要引用C实例的集合;C在关系建模中与“桥表”具有相同的作用
Steven A. Lowe

1

通常,当我遇到这样的情况时会感到很尴尬,但是我不能完全说出原因,这是因为我正在尝试将某些东西放在错误的类中。几乎总是有一种方法可以将某些功能移出类AB使其更加清晰。

将关联移动到的一个候选对象是首先创建关联的代码。也许而不是调用attach()它只是存储一个列表std::pair<A, B>。如果存在多个创建关联的地方,则可以将其抽象为一个类。

Z在您的情况下,另一个移动的候选对象是拥有关联对象的对象。

将关联移动到的另一个常见候选对象是经常在或对象上调用方法的代码。例如,它不是在内部调用所有关联对象的方法,而是调用,而是已经知道关联并为每个对象调用。同样,如果有多个地方调用它,则可以将其抽象为一个类。ABa->foo()Ba->foo(b)

很多时候,创建,拥有和使用关联的对象恰好是同一对象。这样可以使重构非常容易。

最后一种方法是从其他关系派生该关系。例如,兄弟姐妹是多对多的关系,但是您不是从兄弟班级中列出姐妹列表,反之亦然,而是从父母关系中得出该关系。这种方法的另一个优点是,它创建了一个真实的来源。错误或运行时错误不可能在兄弟,姐妹和父母列表之间造成差异,因为您只有一个列表。

这种重构并不是每次都能奏效的魔术公式。在找到适合每种情况的理想选择之前,您仍然需要进行试验,但是我发现它可以在95%的时间内正常工作。剩下的时间只需要忍受尴尬。


0

如果执行此操作,则将Attach放入A和B中。在Attach方法内部,然后对传入的对象调用Attach。这样,您可以调用A.Attach(B)或B.Attach(一个)。我的语法可能不正确,多年来我没有使用过c ++:

class A {
  private: std::vector<B *> Bs;

  void Attach (B* obj) {
    // Only add obj if it's not in the vector
    if (std::find(Bs.begin(), Bs.end(), obj) == Bs.end()) {
      //Add obj to the vector
      Bs.push_back(obj);

      // Attach this class to obj
      obj->Attach(this);
    }
  }    
}

class B {
  private: std::vector<A *> As;

  void Attach (A* obj) {
    // Only add obj if it's not in the vector
    if (std::find(As.begin(), As.end(), obj) == As.end()) {
      //Add obj to the vector
      As.push_back(obj);

      // Attach this class to obj
      obj->Attach(this);
    }
  }    
}

另一种方法是创建第3类C,它管理AB配对的静态列表。


只是一个关于您的备选建议的问题,即拥有3类C来管理AB对列表:A和B如何知道其对成员?A和B都需要链接到(单个)C,然后要求C查找合适的对吗?B :: Foo(){std :: vector <A *> As = C.GetAs(this); / *使用As ...做某事* /}还是您设想了其他事情?因为这似乎令人费解。
德米特里(Dmitri Shuralyov)2012年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.