เป็นวิธีที่ดีในการแสดงความสัมพันธ์แบบหลายต่อหลายระหว่างสองชั้นคืออะไร?


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 :: เป็นเวกเตอร์หรือไม่

หรือควรเป็น 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 ซึ่งเป็นของ Z ตามที่คุณเห็นมันจะกลายเป็นเรื่องที่ซับซ้อนมากเมื่อพยายามจัดการลิงก์อื่น ๆ A และ B จำเป็นต้องสามารถเข้าถึงรายการคู่ที่เชื่อมโยงกับได้อย่างรวดเร็ว
Dmitri Shuralyov

หากคุณอยากรู้เกี่ยวกับชื่อจริงของ A และ B อยู่ในสถานการณ์ของพวกเขามีและPointer GestureRecognizerพอยน์เตอร์เป็นเจ้าของและจัดการโดยคลาส InputManager GestureRecognizers เป็นเจ้าของโดยอินสแตนซ์ของ Widget ซึ่งจะถูกเปิดใช้โดยอินสแตนซ์ของหน้าจอซึ่งเป็นเจ้าของโดยอินสแตนซ์ของแอป พอยน์เตอร์ได้รับมอบหมายให้กับ GestureRecognizers เพื่อให้สามารถป้อนข้อมูลดิบให้กับพวกเขาได้ แต่ GestureRecognizers จำเป็นต้องตระหนักถึงจำนวนพอยน์เตอร์ที่เกี่ยวข้องกับพวกเขาในปัจจุบัน (เพื่อแยกแยะ 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ชั้นเรียนที่ทั้งสองAและBสืบทอดมาดังนั้นหลีกเลี่ยงการทำซ้ำตัวเองและทำคะแนนคะแนนโบนัสในวันพิพากษา แต่แม้กระทั่งวิธีการติดตั้งที่ไม่ซับซ้อนซับซ้อนก็จะทำให้งานสำเร็จลุล่วงได้


1
นี่ฟังดูเหมือนวิธีที่ฉันคิดในใจ (เช่นใส่ Attach () ทั้ง A และ B) ฉันยังชอบทางออก DRY ที่มากขึ้น (ฉันไม่ชอบโค้ดที่ซ้ำกันจริงๆ) ของการมีคลาส MutuallyAttachable ที่คุณสามารถหาได้จากเมื่อใดก็ตามที่ต้องการพฤติกรรมนี้ (ฉันคาดหวังบ่อยครั้ง) แค่เห็นโซลูชันเดียวกันที่เสนอโดยคนอื่นทำให้ฉันมีความมั่นใจมากขึ้นในนั้นขอบคุณ!
Dmitri Shuralyov

ยอมรับสิ่งนี้เพราะ IMO เป็นทางออกที่ดีที่สุดที่มีให้ ขอบคุณ! ฉันจะนำไปใช้ในชั้นเรียนที่สามารถเข้าถึงได้ร่วมกันและรายงานที่นี่ถ้าฉันพบปัญหาที่ไม่คาดฝันใด ๆ (ปัญหาบางอย่างอาจเกิดขึ้นเนื่องจากมรดกที่หลากหลายซึ่งตอนนี้ฉันยังไม่ได้มีประสบการณ์มาก)
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 จะต้องมีความสัมพันธ์แบบ 1 ต่อหลายกับ C ใช่ไหม?
Dmitri Shuralyov

1
A และ B ทั้งสองต้องการการอ้างอิงถึงการรวบรวมอินสแตนซ์ C C ทำหน้าที่จุดประสงค์เดียวกันกับ 'table bridge' ในการสร้างแบบจำลองเชิงสัมพันธ์
Steven A. Lowe

1

โดยปกติเมื่อฉันเข้าสู่สถานการณ์เช่นนี้ที่รู้สึกอึดอัดใจ แต่ฉันไม่สามารถบอกได้ว่าทำไมเพราะเป็นเพราะฉันพยายามใส่อะไรบางอย่างผิดปกติ มีวิธีที่จะย้ายฟังก์ชั่นบางอย่างออกจากชั้นเรียนAและBทำให้สิ่งต่าง ๆ ชัดเจนยิ่งขึ้น

หนึ่งผู้สมัครสำหรับการย้ายการเชื่อมโยงไปเป็นรหัสที่สร้างการเชื่อมโยงในสถานที่แรก บางทีแทนที่จะเรียกมันก็เก็บรายการattach() std::pair<A, B>หากมีหลายสถานที่ที่สร้างการเชื่อมโยงจะสามารถแยกออกเป็นหนึ่งคลาส

ตัวเลือกอื่นสำหรับการย้ายคือวัตถุที่เป็นเจ้าของวัตถุที่เกี่ยวข้องZในกรณีของคุณ

อีกตัวเลือกทั่วไปสำหรับการย้ายการเชื่อมโยงไปเป็นรหัสที่เรียกวิธีAหรือBวัตถุ ตัวอย่างเช่นแทนที่จะโทรหาa->foo()ภายในซึ่งทำอะไรบางอย่างกับBวัตถุที่เกี่ยวข้องทั้งหมดภายในจะทราบถึงการเชื่อมโยงและการโทรa->foo(b)สำหรับแต่ละรายการ อีกครั้งหากมีหลายสถานที่เรียกว่ามันสามารถแยกเป็นชั้นเดียว

หลายครั้งที่วัตถุที่สร้างเป็นเจ้าของและใช้ความสัมพันธ์เกิดขึ้นเป็นวัตถุเดียวกัน ที่สามารถทำให้การปรับโครงสร้างทำได้ง่ายมาก

วิธีสุดท้ายคือการได้รับความสัมพันธ์จากความสัมพันธ์อื่น ๆ ตัวอย่างเช่นพี่น้องเป็นความสัมพันธ์แบบกลุ่มต่อกลุ่ม แต่แทนที่จะเก็บรายชื่อพี่น้องในชั้นพี่น้องและในทางกลับกันคุณได้รับความสัมพันธ์นั้นจากความสัมพันธ์ของผู้ปกครอง ข้อดีอีกอย่างของวิธีนี้คือมันสร้างแหล่งความจริงเพียงแหล่งเดียว ไม่มีวิธีที่เป็นไปได้สำหรับข้อผิดพลาดหรือข้อผิดพลาด runtime เพื่อสร้างความแตกต่างระหว่างรายการพี่ชายน้องสาวและผู้ปกครองเพราะคุณมีเพียงหนึ่งรายการ

การรีแฟคเจอริ่งชนิดนี้ไม่ใช่สูตรวิเศษที่ใช้งานได้ทุกครั้ง คุณยังคงต้องทดสอบจนกว่าคุณจะพบแบบที่ดีสำหรับแต่ละสถานการณ์ แต่ฉันพบว่ามันทำงานได้ประมาณ 95% ของเวลา เวลาที่เหลืออยู่คุณต้องอยู่กับความอึดอัด


0

ถ้าฉันใช้สิ่งนี้ฉันจะใส่ Attach ทั้ง A และ B ด้านในของ Attach method ฉันจะเรียก Attach บนวัตถุที่ส่งผ่านด้วยวิธีนี้คุณสามารถเรียกทั้ง A.Attach (B) หรือ B.Attach ( A) ไวยากรณ์ของฉันอาจไม่ถูกต้องฉันไม่ได้ใช้ 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


เพียงแค่คำถามเกี่ยวกับข้อเสนอแนะทางเลือกของคุณในการมีคลาส C ลำดับที่ 3 เพื่อจัดการรายการคู่ AB: A และ B จะรู้จักสมาชิกคู่ได้อย่างไร แต่ละ A และ B ต้องการลิงค์ไปที่ (single) C หรือไม่จากนั้นให้ C ค้นหาคู่ที่เหมาะสม B :: Foo () {std :: vector <A *> As = C.GetAs (this); / * ทำบางสิ่งด้วย As ... * /} หรือคุณคิดอย่างอื่นอีกมั้ย เพราะสิ่งนี้ดูเหมือนจะซับซ้อนอย่างมาก
Dmitri Shuralyov
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.