두 클래스 사이의 다 대다 관계를 나타내는 좋은 방법은 무엇입니까?


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의 저장소입니다. 실제 예제가 없으면 다른 방법을 볼 수 없습니다.
Machado

1
더 정확하게 말하면 A와 B의 소유자가 다를 수 있습니다. 아마도 A는 Z가 소유하고 B는 X가 소유하고, X는 Z가 소유합니다. 보시다시피 다른 곳에서 링크를 관리하려고하면 실제로 복잡해집니다. A와 B는 연결된 쌍의 목록에 빠르게 액세스 할 수 있어야합니다.
Dmitri Shuralyov

내 상황에서 A와 B의 실제 이름에 대해 궁금한 점은 Pointerand GestureRecognizer입니다. 포인터는 InputManager 클래스가 소유하고 관리합니다. GestureRecognizer는 위젯 인스턴스가 소유하며, 위젯 인스턴스는 App 인스턴스가 소유 한 Screen 인스턴스가 소유합니다. 포인터는 GestureRecognizer에 할당되어 원시 입력 데이터를 공급할 수 있지만 GestureRecognizer는 현재 연관된 몇 개의 포인터를 인식해야합니다 (1 손가락과 2 손가락 제스처 등을 구별하기 위해).
Dmitri Shuralyov

이제 그것에 대해 더 많이 생각하기 때문에 포인터 기반이 아닌 프레임 기반 제스처 인식을하려고하는 것 같습니다. 따라서 한 번에 포인터가 아닌 한 번에 제스처로 제스처에 이벤트를 전달할 수도 있습니다. 흠.
Dmitri Shuralyov

답변:


2

한 가지 방법은 각 클래스에 공개 Attach()메소드와 보호 AttachWithoutReciprocating()메소드를 추가하는 것 입니다. 그들의 방법으로 상대방의 전화를 걸 수 있도록 친구를 A만들고 B서로 사귀 십시오 .Attach()AttachWithoutReciprocating()

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

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

에 대해 비슷한 메소드를 구현 B하면 호출 할 클래스를 기억할 필요가 없습니다 Attach().

나는 확실히 당신이에 그 동작을 마무리 할 수있어 MutuallyAttachable수업이 모두 AB상속하여 자신을 반복하고 심판의 날에 보너스 포인트를 득점 피에서. 그러나 정교하지 않은 두 위치에서 구현 방법으로도 작업이 완료됩니다.


1
이것은 내가 생각했던 해결책처럼 정확하게 들립니다 (즉, A와 B에 Attach ()를 넣는 것). 나는 또한이 행동이 필요할 때마다 파생 될 수있는 MutuallyAttachable 클래스를 갖는 DRY 솔루션 (코드 중복을 싫어합니다)을 정말로 좋아합니다 (예상합니다). 다른 사람이 제공하는 것과 동일한 솔루션을 보는 것만으로도 훨씬 더 자신감을 가질 수 있습니다. 감사합니다!
Dmitri Shuralyov

IMO이기 때문에 이것을 받아들이는 것은 지금까지 제공된 최고의 솔루션입니다. 감사! 나는 그 MutuallyAttachable 클래스를 지금 구현하고 예기치 않은 문제가 발생하면 여기에보고 할 것입니다 (아직 경험이 많지 않은 다중 상속으로 인해 일부 어려움이 발생할 수도 있음).
Dmitri Shuralyov

모든 것이 순조롭게 진행되었습니다. 그러나 원래 질문에 대한 마지막 의견에 따라 원래 구상 한 기능 에이 기능이 필요하지 않다는 것을 깨닫기 시작했습니다. 이 양방향 연결을 추적하는 대신 모든 쌍을 매개 변수로 필요한 기능에 매개 변수로 전달합니다. 그러나 나중에도 여전히 유용 할 수 있습니다.
Dmitri Shuralyov

MutuallyAttachable누군가가 그것을 재사용하고 싶다면이 작업을 위해 작성한 클래스는 다음과 같습니다 . goo.gl/VY9RB (Pastebin) 편집 :이 코드는 템플릿 클래스로 만들어서 향상시킬 수 있습니다.
Dmitri Shuralyov

1
MutuallyAttachable<T, U>수업 템플릿을 Gist에 배치했습니다 . gist.github.com/3308058
Dmitri Shuralyov

5

그것은 가능하다 관계 자체가 추가 속성을 가지고?

그렇다면 별도의 클래스 여야합니다.

그렇지 않은 경우 왕복 목록 관리 메커니즘이면 충분합니다.


별도의 클래스라면 A는 연결된 B의 인스턴스를 어떻게 알 수 있고 B는 연결된 A의 인스턴스를 어떻게 알 수 있습니까? 이 시점에서 A와 B는 C와 일대 다 관계를 가져야합니다. 그렇지 않습니까?
Dmitri Shuralyov 1

1
A와 B는 모두 C 인스턴스 모음에 대한 참조가 필요합니다. C는 관계형 모델링에서 '브리지 테이블'과 동일한 목적을 수행합니다
.

1

일반적으로 어색한 느낌이 드는 상황에 처했을 때 이유를 알 수없는 이유는 잘못된 수업에 무언가를 넣으려고하기 때문입니다. 클래스에서 일부 기능을 이동하는 방법은 거의 항상있다 AB일을 더 명확하게하기는.

연관을 이동시키는 후보 중 하나는 우선 연관을 작성하는 코드입니다. 아마도 attach()그것을 호출 하는 대신 단순히의 목록을 저장합니다 std::pair<A, B>. 연관을 작성하는 여러 위치가있는 경우 하나의 클래스로 추상화 할 수 있습니다.

이동의 또 다른 후보 Z는 귀하의 경우 관련된 개체를 소유 한 개체입니다 .

연관을 이동시키는 또 다른 일반적인 후보는 메소드 또는 오브젝트 에서 자주 호출 하는 코드입니다 . 예를 들어 내부적으로 모든 관련 객체로 무언가를 수행하는 을 호출하는 대신 이미 연결과 각 객체에 대한 호출 을 알고 있습니다. 다시 말하지만, 여러 장소에서 호출하면 단일 클래스로 추상화 될 수 있습니다.ABa->foo()Ba->foo(b)

연관을 작성, 소유 및 사용하는 오브젝트가 동일한 오브젝트 인 경우가 많습니다. 리팩토링을 매우 쉽게 할 수 있습니다.

마지막 방법은 다른 관계에서 관계를 도출하는 것입니다. 예를 들어, 형제 자매는 다 대다 관계이지만 형제 자매의 명단을 유지하거나 그 반대가 아니라 부모 관계에서 그 관계를 도출합니다. 이 방법의 다른 장점은 단일 소스를 생성한다는 것입니다. 하나의 목록 만 있기 때문에 버그 나 런타임 오류가 형제, 자매 및 부모 목록간에 불일치를 만드는 방법은 없습니다.

이런 종류의 리팩토링은 매번 작동하는 마술 공식이 아닙니다. 각 개별 상황에 적합 할 때까지 실험을해야하지만 시간의 약 95 %가 효과가있는 것으로 나타났습니다. 남은 시간은 어색함과 함께 살아야합니다.


0

이것을 구현하고 있다면 A와 B에 Attach를 둘 것입니다. 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);
    }
  }    
}

다른 방법은 고정 된 AB 쌍 목록을 관리하는 C 클래스 인 C를 작성하는 것입니다.


AB 페어 목록을 관리하기 위해 3 급 C를 사용하는 다른 제안에 관한 질문 : A와 B가 페어 멤버를 어떻게 알 수 있습니까? 각 A와 B에 (단일) C에 대한 링크가 필요합니까? 그렇다면 C에게 적절한 쌍을 찾도록 요청 하시겠습니까? B :: Foo () {std :: vector <A *> As = C.GetAs (this); / * As로 무언가를합니까 ... * /} 다른 것을 구상 했습니까? 이것이 엉망인 것처럼 보이기 때문입니다.
Dmitri Shuralyov 21:06에
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.