10

Let's say I have two object types, A and B. The relationship between them is many-to-many, but neither of them is the owner of the other.

Both A and B instances need to be aware of the connection; it's not just one way.

So, we can do this:

class A
{
    ...

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

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

My question is: where do I put the functions to create and destroy the connections?

Should it be A::Attach(B), which then updates A::Bs and B::As vectors?

Or should it be B::Attach(A), which seems equally reasonable.

Neither of those feels right. If I stop working with the code, and come back after a week, I'm sure I won't be able to recall if I should be doing A.Attach(B) or B.Attach(A).

Perhaps it should be a function like this:

CreateConnection(A, B);

But making a global function seems undesirable also, given that it's a function specifically for working with only classes A and B.

Another question: if I run into this problem/requirement often, can I somehow make a general solution for it? Perhaps a TwoWayConnection class that I can derive from or use within classes that share this type of relationship?

What are some good ways to handle this situation... I know how to handle the one-to-many "C owns D" situation quite well, but this one is trickier.

Edit: Just to make it more explicit, this question doesn't involve ownership issues. Both A and B are owned by some other object Z, and Z takes care of all ownership issues. I'm only interested in how to create/remove the many-to-many links between A and B.

  • I would create a Z::Attach(A, B), so, if Z owns the two kinds of objects, "feels right", for him to create the connection. In my (not-so-good) example, Z would be a repository for either A and B. I can't see another way without a pratical example. – Machado May 10 '12 at 21:10
  • 1
    To be more precise, A and B may have different owners. Perhaps A is owned by Z, whereas B is owned by X, which is in turn owned by Z. As you can see, it then becomes really convoluted trying to manage the link elsewhere. A and B need to be able to quickly access a list of the pairs it's linked to. – Dmitri Shuralyov May 10 '12 at 21:14
  • If you're curious about the real names of A and B in my situation, they are Pointer and GestureRecognizer. Pointers are owned and managed by the InputManager class. GestureRecognizers are owned by Widget instances, which are in turned owned by a Screen instance which is owned by an App instance. Pointers get assigned to GestureRecognizers so that they can feed raw input data to them, but GestureRecognizers need to be aware of how many pointers there are currently associated with them (to distinguish 1 finger vs. 2 finger gestures, etc.). – Dmitri Shuralyov May 10 '12 at 21:24
  • Now that I think about that more, it seems that I'm just trying to do frame-based (rather than pointer-based) gesture recognition. So I might as well just pass events to gesture frame-at-a-time rather than pointer-at-a-time. Hmm. – Dmitri Shuralyov May 10 '12 at 21:36
2

One way is to add a public Attach() method and also a protected AttachWithoutReciprocating() method to each class. Make A and B mutual friends so that their Attach() methods can call the other's AttachWithoutReciprocating():

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

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

If you implement similar methods for B, you won't have to remember which class to call Attach() on.

I'm sure you could wrap that behavior up in a MutuallyAttachable class that both A and B inherit from, thus avoiding repeating yourself and scoring bonus points on Judgement Day. But even the unsophisticated implement-it-in-both-places approach will get the job done.

  • 1
    This sounds precisely like the solution I was thinking of in the back of my mind (i.e. to put Attach() in both A and B). I also really like the more DRY solution (I really dislike duplicating code) of having a MutuallyAttachable class that you can derive from whenever this behaviour is needed (I foresee quite often). Just seeing the same solution offered by someone else makes me that much more confident in it, thanks! – Dmitri Shuralyov May 10 '12 at 21:04
  • Accepting this because IMO it's the best solution offered so far. Thanks! I'm going to implement that MutuallyAttachable class now and report here if I run into any unforeseen issues (some difficulties may also arise due to the now-multiple inheritance which I don't have much experience with yet). – Dmitri Shuralyov May 10 '12 at 21:16
  • Everything worked out smoothly. However, as per my last comment on the original question, I'm starting to realize I don't need this functionality for what I originally envisioned. Instead of keeping track of these 2-way connections, I will just pass every pair as a parameter to the function that needs it. However, this may still come in handy at a later time. – Dmitri Shuralyov May 11 '12 at 14:40
  • Here's the MutuallyAttachable class I wrote for this task, if anyone wants to reuse it: goo.gl/VY9RB (Pastebin) Edit: This code could be improved by making it a template class. – Dmitri Shuralyov May 11 '12 at 14:45
  • 1
    I've placed the MutuallyAttachable<T, U> class template at Gist. gist.github.com/3308058 – Dmitri Shuralyov Aug 9 '12 at 21:13
5

Is it possible for the relationship itself to have additional properties?

If so, then it should be a separate class.

If not, then any reciprocating list-management mechanism will suffice.

  • If it's a separate class, how would A know its linked instances of B, and B know its linked instances of A? At that point, both A and B would need to have a 1-to-many relationship with C, wouldn't they? – Dmitri Shuralyov May 11 '12 at 1:20
  • 1
    A and B would both need a reference to the collection of C instances; C serves the same purpose as a 'bridge table' in relational modeling – Steven A. Lowe May 11 '12 at 17:09
1

Usually when I get into situations like this that feel awkward, but I can't quite tell why, it's because I'm trying to put something in the wrong class. There's almost always a way to move some functionality out of class A and B to make things more clear.

One candidate for moving the association to is the code that creates the association in the first place. Maybe instead of calling attach() it simply stores a list of std::pair<A, B>. If there are multiple places creating associations it can be abstracted out into one class.

Another candidate for a move is the object that owns the associated objects, Z in your case.

Another common candidate for moving the association to is the code that is frequently calling methods on A or B objects. For example, instead of calling a->foo(), which internally does something with all associated B objects, it already knows the associations and calls a->foo(b) for each one. Again, if multiple places call it, it can be abstracted into a single class.

Lots of times the objects that create, own, and use the association happen to be the same object. That can make the refactoring very easy.

The last method is to derive the relationship from other relationships. For example, brothers and sisters are a many-to-many relationship, but rather than keeping a list of sisters in the brother class and vice versa, you derive that relationship from the parental relationship. The other advantage of this method is that it creates a single source of truth. There's no possible way for a bug or runtime error to create a discrepancy between the brother, sister, and parent lists, because you only have the one list.

This kind of refactoring isn't a magic formula that works every time. You still have to experiment until you find a good fit for each individual circumstance, but I've found it to work about 95% of the time. The remaining times you just have to live with the awkwardness.

0

If I were implementing this, I would put Attach in both A and B. Inside of the Attach method, I would then call Attach on the object passed in. That way you can call either A.Attach(B) or B.Attach(A). My syntax may not be correct, I haven't used c++ in years:

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);
    }
  }    
}

An alternate method would be to create a 3rd class, C which manages a static list of A-B pairings.

  • Just a question regarding your alternative suggestion of having a 3rd class C to manage a list of A-B pairs: How would A and B know its pair members? Would each A and B need a link to (single) C, then ask C to find the appropriate pairs? B::Foo() { std::vector<A *> As = C.GetAs(this); /* Do something with As... */ } Or did you envision something else? Because this seems awfully convoluted. – Dmitri Shuralyov May 10 '12 at 21:06

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.