การพึ่งพาคลาสแบบวงกลม


12

การออกแบบไม่ดีหรือไม่ที่มี 2 คลาสที่ต้องใช้ซึ่งกันและกัน?

ฉันกำลังเขียนเกมเล็ก ๆ ที่ฉันมีGameEngineชั้นเรียนที่มีGameStateวัตถุบางอย่าง ในการเข้าถึงวิธีการแสดงผลหลายวิธีGameStateวัตถุเหล่านี้จำเป็นต้องทราบGameEngineคลาสด้วย - ดังนั้นจึงเป็นการอ้างอิงแบบวนรอบ

คุณจะเรียกการออกแบบที่ไม่ดีนี้หรือไม่? ฉันแค่ถามเพราะฉันไม่แน่ใจและในเวลานี้ฉันยังสามารถ refactor สิ่งเหล่านี้


2
ฉันจะแปลกใจมากถ้าคำตอบคือใช่
doppelgreener

เนื่องจากมีคำถามสองข้อที่แยกกันอย่างมีเหตุผล .. คำตอบคือใช่สำหรับหนึ่งในนั้น ทำไมคุณต้องการออกแบบสถานะที่ทำอะไรบางอย่างจริง ๆ คุณควรมีผู้จัดการ / ผู้ควบคุมเพื่อตรวจสอบสถานะและดำเนินการ .. มันค่อนข้างสับสนที่จะให้วัตถุประเภทรัฐเข้ามาแทนที่ความรับผิดชอบอย่างอื่น ถ้าคุณอยากจะทำมัน, C ++ เป็นของเพื่อน
Teodron

คลาส GameState ของฉันเป็นสิ่งที่เหมือนกับอินโทรเกมจริงเมนูและอื่น ๆ GameEngine เก็บสถานะเหล่านี้ไว้ในสแต็คดังนั้นฉันสามารถหยุดสถานะชั่วคราวและเปิดเมนูหรือเล่น cutscene, ... คลาส GameState จำเป็นต้องรู้จัก GameEngine เพราะเอ็นจิ้นสร้างหน้าต่างและมีบริบทการเรนเดอร์ .
shad0w

5
โปรดจำไว้ว่ากฎเหล่านี้มีจุดมุ่งหมายเพื่อความรวดเร็ว รวดเร็วในการทำงานรวดเร็วในการสร้างรวดเร็วในการบำรุงรักษา บางครั้งปัจจัยเหล่านี้ขัดแย้งกับกันและกันและคุณจำเป็นต้องทำการวิเคราะห์ต้นทุนและผลกำไรเพื่อตัดสินใจว่าจะดำเนินการอย่างไร หากคุณคิดเกี่ยวกับมันมากขนาดนั้นและโทรออกไปด้วยดีคุณยังคงทำได้ดีกว่า 90% ของผู้พัฒนา ไม่มีสัตว์ประหลาดที่ซ่อนอยู่ที่จะฆ่าคุณถ้าคุณทำผิดเขาเป็นผู้ดำเนินการตู้เก็บเงินที่ซ่อนอยู่
DampeS8N

คำตอบ:


0

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

ในกรณีของคุณมันจะดีกว่าที่จะมีสองคลาสแยกต่างหากสำหรับ GameEnigne และ GameState เนื่องจากโดยทั่วไปแล้วทั้งสองคนกำลังทำสิ่งที่แตกต่างกันและหลังจากนั้นคุณอาจกำหนดคลาสต่างๆที่สืบทอด GameState (เช่น GameState สำหรับแต่ละฉากในเกมของคุณ) และไม่มีใครสามารถปฏิเสธความต้องการของพวกเขาในการเข้าถึงซึ่งกันและกัน โดยทั่วไปแล้ว GameEngine กำลังรันเกมดังนั้นมันควรจะมีตัวชี้ และ GameState กำลังใช้ทรัพยากรที่กำหนดไว้ใน GameEngine (เช่นเรนเดอร์ตัวจัดการฟิสิกส์ ฯลฯ )

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

จนถึงตอนนี้เรารู้ว่าเราต้องการการพึ่งพาแบบวนในการออกแบบ มีหลายวิธีในการสร้างที่ปลอดภัย:

  1. ตามที่คุณแนะนำเราสามารถใส่ตัวชี้ของแต่ละตัวเป็นตัวเลือกที่ง่ายที่สุด แต่อาจควบคุมไม่ได้
  2. คุณยังสามารถใช้การออกแบบซิงเกิลตัน / มัลติตันด้วยวิธีนี้คุณควรกำหนดคลาส GameEngine ของคุณเป็นซิงเกิลตันและแต่ละ GameStates เหล่านั้นเป็นมัลติตัน แม้ว่านักพัฒนาซอฟต์แวร์จำนวนมากมองว่าทั้ง Singleton และ Multiton เป็น AntiPattern
  3. คุณสามารถใช้ตัวแปรทั่วโลกเฮ้เป็นสิ่งเดียวกับ Singleton / multiton แต่พวกเขาแตกต่างกันเล็กน้อยในสิ่งที่พวกเขาไม่ได้ จำกัด โปรแกรมเมอร์ไม่ให้สร้างอินสแตนซ์ที่จะ

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


6

การออกแบบไม่ดีหรือไม่ที่มี 2 คลาสที่ต้องใช้ซึ่งกันและกัน?

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

สิ่งที่มี C ++ คือการพึ่งพาแบบวนรอบจะไม่รวบรวมได้อย่างง่ายดายดังนั้นจึงเป็นความคิดที่ดีกว่าที่จะกำจัดพวกเขาแทนที่จะใช้เวลาในการแก้ไขการรวบรวมของคุณ

ดูคำถามนี้ใน SOเพื่อความคิดเห็นเพิ่มเติม

คุณจะเรียกการออกแบบที่ไม่ดี [ออกแบบของฉัน] ไหม

ไม่มันยังดีกว่าใส่ทุกอย่างในชั้นเดียว

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

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

ตอนนี้อาจมีการออกแบบขั้นสุดท้าย ฉันอยากรู้ว่าคำตอบอื่นนำความคิดที่ดีมาหนึ่งข้อหรือไม่ ถึงกระนั้นคุณก็อาจเป็นผู้ที่สามารถหาดีไซน์ที่ดีที่สุดสำหรับเกมของคุณได้


ทำไมมันถึงเป็นกลิ่นรหัส? วัตถุส่วนใหญ่ที่มีความสัมพันธ์แบบพ่อแม่ลูกต้องเห็นกัน
Kikaimaru

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

นั่นเป็นความเข้าใจผิดที่ลื่น คลาสสแตติกสามารถนำไปสู่สิ่งที่ไม่ดีได้เช่นกัน แต่คลาสที่ใช้ประโยชน์จะไม่มีกลิ่นรหัส และฉันสงสัยจริงๆว่ามีวิธีที่ "ดี" ในการทำสิ่งต่าง ๆ เช่น Scene มี SceneNodes และ SceneNode มีการอ้างอิงถึง Scene ดังนั้นคุณจึงไม่สามารถเพิ่มลงในสองฉากที่แตกต่างกัน ... และคุณจะหยุดที่ไหน จำเป็นต้องใช้ B, B ต้องใช้ C และ C ต้องมีกลิ่นรหัสหรือไม่?
Kikaimaru

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

ไม่มีการกล่าวถึงกรณีนี้ในบทความวิกิพีเดีย และคุณไม่สามารถพูดได้ว่าเป็น "ความไม่เหมาะสมทางเพศ" จนกว่าคุณจะได้เห็นชั้นเรียนเหล่านั้น
Kikaimaru

6

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

อย่างไรก็ตามมันเป็นเรื่องธรรมดามากที่วัตถุ 2 ชิ้นจะอ้างถึงซึ่งกันและกันและความแตกต่างตรงนี้ก็คือความสัมพันธ์ในทิศทางเดียวนั้นมักจะเป็นนามธรรมมากกว่า ตัวอย่างเช่นในระบบ Model-View-Controller วัตถุ View อาจเก็บการอ้างอิงไปยังวัตถุ Model และจะรู้ทุกอย่างเกี่ยวกับมันความสามารถในการเข้าถึงวิธีการและคุณสมบัติทั้งหมดเพื่อให้ View สามารถเติมข้อมูลของตัวเองด้วยข้อมูลที่เกี่ยวข้อง . วัตถุรุ่นอาจเก็บข้อมูลอ้างอิงกลับไปที่มุมมองเพื่อให้สามารถทำการปรับปรุงดูเมื่อข้อมูลของตัวเองมีการเปลี่ยนแปลง แต่แทนที่จะเป็นรุ่นที่มีการอ้างอิงมุมมอง - ซึ่งจะทำให้รูปแบบขึ้นอยู่กับมุมมอง - โดยปกติมุมมองจะใช้ส่วนต่อประสานที่สังเกตได้ซึ่งมักจะมีเพียง 1Update()ฟังก์ชั่นและรูปแบบถือการอ้างอิงไปยังวัตถุที่สังเกตได้ซึ่งอาจจะเป็นมุมมอง เมื่อโมเดลเปลี่ยนแปลงจะเรียกUpdate()ใช้ Observables ทั้งหมดและ View จะดำเนินการUpdate()โดยเรียกกลับไปที่ Model และดึงข้อมูลที่อัพเดตแล้ว ประโยชน์ที่นี่คือตัวแบบไม่ทราบอะไรเกี่ยวกับ Views เลย (และทำไมจึงควรใช้?) และสามารถนำกลับมาใช้ใหม่ในสถานการณ์อื่น ๆ แม้แต่ผู้ที่ไม่มี Views

คุณมีสถานการณ์คล้ายกันในเกมของคุณ โดยปกติแล้ว GameEngine จะรู้เกี่ยวกับ GameStates แต่ GameState ไม่จำเป็นต้องรู้ทั้งหมดเกี่ยวกับ GameEngine - เพียงแค่ต้องการเข้าถึงวิธีการเรนเดอร์บางอย่างใน GameEngine สิ่งนั้นควรตั้งเตือนเล็กน้อยในหัวของคุณที่บอกว่า (a) GameEngine พยายามทำสิ่งต่าง ๆ มากเกินไปในคลาสเดียวและ / หรือ (b) GameState ไม่ต้องการเอ็นจิ้นเกมทั้งหมดเพียงแค่ส่วนที่สามารถแสดงผลได้

สามวิธีในการแก้ไขปัญหานี้ ได้แก่ :

  • ทำให้ GameEngine สืบทอดมาจากอินเตอร์เฟส Renderable และส่งผ่านการอ้างอิงนั้นไปยัง GameState หากคุณปรับโครงสร้างส่วนการแสดงผลอีกครั้งคุณเพียงแค่ต้องแน่ใจว่าส่วนดังกล่าวยังคงอยู่ในส่วนต่อประสานเดิม
  • แยกส่วนการเรนเดอร์เป็นออบเจ็กต์ Renderer ใหม่และส่งผ่านไปยัง GameStates แทน
  • ปล่อยให้มันเป็น บางทีในบางจุดคุณอาจต้องการเข้าถึงฟังก์ชั่น GameEngine ทั้งหมดจาก GameState หลังจากทั้งหมด แต่โปรดจำไว้ว่าซอฟต์แวร์ที่ง่ายต่อการบำรุงรักษาและง่ายต่อการขยายมักจะต้องการให้แต่ละคลาสอ้างถึงภายนอกน้อยที่สุดเท่าที่จะเป็นไปได้ซึ่งเป็นสาเหตุที่ทำให้สิ่งต่าง ๆ แตกย่อยเป็นวัตถุย่อย งานที่กำหนดไว้จะดีกว่า

0

โดยทั่วไปถือว่าเป็นวิธีปฏิบัติที่ดีที่จะมีการเชื่อมโยงสูงกับการมีเพศสัมพันธ์ต่ำ นี่คือลิงค์บางส่วนเกี่ยวกับการแต่งงานและการติดต่อกัน

การมีเพศสัมพันธ์วิกิพีเดีย

การทำงานร่วมกันของวิกิพีเดีย

การมีเพศสัมพันธ์ต่ำ, การทำงานร่วมกันสูง

stackoverflow เกี่ยวกับแนวปฏิบัติที่ดีที่สุด

การมีสองคลาสอ้างอิงถึงกันมีข้อต่อสูง กรอบ Google Guiceมีจุดมุ่งหมายเพื่อให้เกิดการทำงานร่วมกันกับการมีเพศสัมพันธ์สูงต่ำผ่านวิธีการฉีดพึ่งพา ฉันขอแนะนำให้คุณอ่านเพิ่มเติมในหัวข้ออีกเล็กน้อยจากนั้นทำการโทรของคุณเองตามบริบทของคุณ

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.