Phụ thuộc lớp tròn


12

Có phải là thiết kế xấu khi có 2 lớp cần nhau?

Tôi đang viết một trò chơi nhỏ trong đó tôi có một GameEnginelớp học có một vài GameStateđối tượng. Để truy cập một số phương thức kết xuất, các GameStateđối tượng này cũng cần biết GameEnginelớp - vì vậy đó là một phụ thuộc vòng tròn.

Bạn sẽ gọi đây là thiết kế xấu? Tôi chỉ hỏi, vì tôi không chắc lắm và tại thời điểm này tôi vẫn có thể cấu trúc lại những thứ này.


2
Tôi sẽ rất ngạc nhiên nếu câu trả lời là có.
doppelgreener

Vì có hai câu hỏi, đó là sự rời rạc về mặt logic .. câu trả lời là có cho một trong số chúng. Tại sao bạn muốn thiết kế một trạng thái thực sự làm một cái gì đó? bạn nên có một người quản lý / bộ điều khiển để kiểm tra trạng thái và thực hiện hành động .. Thật khó hiểu khi để một loại đối tượng đảm nhận trách nhiệm của người khác. Nếu bạn muốn làm điều đó, C ++ là bạn của bạn .
teodron

Các lớp GameState của tôi là những thứ giống như phần giới thiệu, trò chơi thực tế, một menu, v.v. GameEngine lưu trữ các trạng thái này trong một ngăn xếp, vì vậy tôi có thể tạm dừng trạng thái và mở menu hoặc chơi đoạn cắt cảnh, ... Các lớp GameState cần biết GameEngine, vì công cụ tạo cửa sổ và có bối cảnh kết xuất .
shad0w

5
Hãy nhớ rằng, tất cả các quy tắc này nhằm mục đích nhanh chóng. Nhanh để chạy, Nhanh để tạo, Nhanh để duy trì. Đôi khi các khía cạnh này mâu thuẫn với nhau và bạn cần thực hiện phân tích lợi ích chi phí để quyết định cách tiến hành. Nếu bạn nghĩ về nó thậm chí nhiều như vậy và thực hiện một cuộc gọi ruột, bạn vẫn đang làm tốt hơn 90% các nhà phát triển. Không có con quái vật ẩn nào sẽ giết bạn nếu bạn làm sai, anh ta là một người điều hành trạm thu phí ẩn.
DampeS8N

Câu trả lời:


0

Bản chất nó không phải là một thiết kế tồi, nhưng nó có thể dễ dàng vượt khỏi tầm kiểm soát. Trong thực tế, bạn đang sử dụng thiết kế đó trong mã hàng ngày của bạn. Ví dụ vector biết đó là trình vòng lặp đầu tiên và trình vòng lặp có con trỏ tới vùng chứa của chúng.

Bây giờ trong trường hợp của bạn, sẽ tốt hơn nhiều khi có hai lớp riêng biệt cho GameEnigne và GameState. Vì về cơ bản hai thứ đó đang làm những việc khác nhau, và sau này bạn có thể định nghĩa rất nhiều lớp kế thừa GameState (như GameState cho mỗi cảnh trong trò chơi của bạn). Và không ai có thể phủ nhận nhu cầu của họ để có quyền truy cập lẫn nhau. Về cơ bản GameEngine đang chạy các game thủ, vì vậy nó nên có một con trỏ tới chúng. Và GameState đang sử dụng các tài nguyên được xác định trong GameEngine (như được kết xuất, trình quản lý vật lý, v.v.).

Bạn không thể và không nên kết hợp hai lớp đó với nhau vì bản chất chúng đang làm những việc khác nhau và việc kết hợp sẽ dẫn đến một lớp rất lớn mà không ai thích.

Cho đến nay chúng ta biết rằng chúng ta cần sự phụ thuộc vòng tròn trong thiết kế ra. Có nhiều cách để tạo ra một cách an toàn:

  1. Như bạn đề xuất, chúng ta có thể đặt một con trỏ của từng con vào một cái khác, đó là giải pháp đơn giản nhất nhưng có thể dễ dàng vượt khỏi tầm kiểm soát.
  2. Bạn cũng có thể sử dụng thiết kế singleton / multiton, với phương thức này, bạn nên định nghĩa lớp GameEngine của mình là một singleton và mỗi GameStates đó là multiton. Mặc dù nhiều nhà phát triển coi cả singleton và multiton là AntiPotype, tôi thích thiết kế kiểu này.
  3. Bạn có thể sử dụng các biến toàn cục, về cơ bản giống như Singleton / multiton nhưng chúng có một chút khác biệt ở khía cạnh chúng không giới hạn lập trình viên không tạo ra các trường hợp theo ý muốn.

Để kết luận câu trả lời của anh ấy, bạn có thể sử dụng bất kỳ phương pháp nào trong ba phương pháp đó nhưng bạn cần cẩn thận không sử dụng quá nhiều trong số chúng vì như tôi đã nói tất cả các thiết kế đó thực sự nguy hiểm và có thể dễ dàng dẫn đến một mã không thể nhầm lẫn.


6

Có phải là thiết kế xấu khi có 2 lớp cần nhau?

Đó là một chút của Mùi Code , nhưng người ta có thể rời đi với nó. Nếu đó là cách dễ dàng hơn và nhanh hơn để khởi động trò chơi của bạn, hãy tiếp tục. Nhưng hãy ghi nhớ điều đó bởi vì có một cơ hội tốt bạn sẽ phải cấu trúc lại nó vào một lúc nào đó.

Vấn đề với C ++ là các phụ thuộc vòng tròn sẽ không được biên dịch dễ dàng như vậy , vì vậy có thể là một ý tưởng tốt hơn để loại bỏ chúng thay vì dành thời gian sửa lỗi biên dịch của bạn.

Xem câu hỏi này trên SO để có thêm một vài ý kiến.

Bạn sẽ gọi [thiết kế của tôi] thiết kế xấu?

Không, nó vẫn tốt hơn là đặt mọi thứ vào một lớp.

Nó không tuyệt lắm, nhưng nó thực sự khá gần với hầu hết các triển khai tôi từng thấy. Thông thường, bạn sẽ có một lớp người quản lý cho các trạng thái trò chơi ( hãy cẩn thận! ) Và một lớp kết xuất, và điều khá phổ biến là chúng là các singletons. Vì vậy, sự phụ thuộc vòng tròn là "ẩn", nhưng nó có khả năng ở đó.

Ngoài ra, như bạn đã nói trong các bình luận, hơi lạ khi các lớp trạng thái trò chơi thực hiện một số loại kết xuất. Họ chỉ nên giữ thông tin trạng thái và kết xuất phải được xử lý bởi trình kết xuất hoặc một số thành phần đồ họa của chính các đối tượng trò chơi.

Bây giờ có thể có thiết kế cuối cùng . Tôi tò mò muốn xem liệu những câu trả lời khác có mang lại một ý tưởng hay không. Tuy nhiên, có lẽ bạn là người có thể tìm thấy thiết kế tốt nhất cho trò chơi của bạn.


Tại sao nó sẽ là một mùi mã? Hầu hết các đối tượng có mối quan hệ cha-con cần nhìn thấy nhau.
Kikaimaru

4
Bởi vì đó là cách dễ nhất để không xác định lớp nào chịu trách nhiệm cho việc gì. Nó dễ dàng kết thúc ở hai lớp được kết hợp sâu sắc đến mức việc thêm mã mới có thể được thực hiện ở một trong hai lớp, vì vậy chúng không còn tách biệt về mặt khái niệm. Đó cũng là một cánh cửa mở cho mã spaghetti: lớp A gọi lớp B cho việc này, nhưng B cần thông tin đó từ A, v.v. Vì vậy, tôi sẽ không nói rằng một đối tượng trẻ em nên biết về cha mẹ của nó. Nếu có thể, sẽ tốt hơn nếu không.
Laurent Couvidou

Đó là một sai lầm dốc trơn trượt. Các lớp tĩnh cũng có thể dẫn đến những điều xấu nhưng các lớp sử dụng không phải là mùi mã. Và tôi thực sự nghi ngờ rằng có một số cách "tốt" để tạo ra những thứ như Cảnh có Cảnh và Mã có tham chiếu đến Cảnh, vì vậy bạn không thể thêm nó vào hai cảnh khác nhau ... Và bạn sẽ dừng ở đâu? Là A yêu cầu B, B yêu cầu C và C yêu cầu A có mùi mã?
Kikaimaru

Thật vậy, điều này giống như các lớp tĩnh. Xử lý cẩn thận, chỉ sử dụng nó nếu bạn phải. Bây giờ tôi đang nói từ kinh nghiệm và tôi không phải là người duy nhất nghĩ theo cách này , nhưng, thực sự, đây là vấn đề quan điểm. Ý kiến ​​của tôi là trong bối cảnh được đưa ra bởi OP, điều này thực sự có mùi.
Laurent Couvidou

Không có đề cập đến trường hợp đó trong bài viết wikipedia đó. Và bạn không thể nói đó là trường hợp "Sự thân mật không phù hợp" cho đến khi bạn thực sự thấy những lớp học đó.
Kikaimaru

6

Nó thường được coi là thiết kế xấu khi phải có 2 lớp liên quan trực tiếp đến nhau, vâng. Về mặt thực tế, việc theo dõi dòng điều khiển thông qua mã có thể khó hơn, quyền sở hữu đối tượng và thời gian tồn tại của chúng có thể phức tạp, điều đó có nghĩa là không lớp nào có thể tái sử dụng mà không có lớp kia, điều đó có nghĩa là luồng điều khiển nên thực sự sống bên ngoài cả hai trong số các lớp này trong lớp 'hòa giải' thứ ba, v.v.

Tuy nhiên, rất phổ biến khi 2 đối tượng đề cập đến nhau và sự khác biệt ở đây là thông thường mối quan hệ theo một hướng sẽ trừu tượng hơn. Ví dụ: trong hệ thống Trình điều khiển chế độ xem mô hình, đối tượng Chế độ xem có thể giữ tham chiếu đến đối tượng Mô hình và sẽ biết tất cả về nó, có thể truy cập tất cả các phương thức và thuộc tính của nó để Chế độ xem có thể tự điền dữ liệu có liên quan . Đối tượng Model có thể giữ tham chiếu trở lại View để nó có thể thực hiện cập nhật View khi dữ liệu của chính nó đã thay đổi. Nhưng thay vì Mô hình có tham chiếu Chế độ xem - điều này sẽ khiến Mô hình phụ thuộc vào Chế độ xem - thường thì Chế độ xem thực hiện giao diện Có thể quan sát được, thường chỉ với 1Update()và Mô hình giữ tham chiếu đến một đối tượng Có thể quan sát, có thể là Chế độ xem. Khi Model thay đổi, nó sẽ gọi Update()tất cả các vật quan sát của nó và View thực hiện Update()bằng cách gọi lại vào Model và lấy bất kỳ thông tin cập nhật nào. Lợi ích ở đây là Mô hình hoàn toàn không biết gì về Chế độ xem (và tại sao nên biết?) Và có thể được sử dụng lại trong các tình huống khác, ngay cả những trường hợp không có Chế độ xem.

Bạn có một tình huống tương tự trong trò chơi của bạn. GameEngine thường sẽ biết về GameStates. Nhưng GameState không cần biết tất cả về GameEngine - nó chỉ cần truy cập vào một số phương thức kết xuất nhất định trên GameEngine. Điều đó sẽ đặt ra một báo động nhỏ trong đầu bạn nói rằng (a) GameEngine đang cố gắng làm quá nhiều thứ trong một lớp và / hoặc (b) GameState không cần toàn bộ công cụ trò chơi, chỉ là phần có thể kết xuất.

Ba cách tiếp cận để giải quyết vấn đề này bao gồm:

  • Tạo GameEngine xuất phát từ giao diện Kết xuất và chuyển tham chiếu đó vào GameState. Nếu bạn từng cấu trúc lại phần kết xuất, bạn chỉ cần đảm bảo nó giữ nguyên giao diện.
  • Nhân tố ra phần kết xuất thành một đối tượng Trình kết xuất mới và chuyển phần đó cho GameStates.
  • Để lại nó như nó là. Rốt cuộc, có thể đôi lúc bạn sẽ muốn truy cập tất cả chức năng GameEngine từ GameState. Nhưng hãy nhớ rằng phần mềm dễ bảo trì và dễ mở rộng thường yêu cầu mỗi lớp tham khảo càng ít bên ngoài càng tốt, đó là lý do tại sao chia mọi thứ thành các đối tượng phụ và giao diện thực hiện một và tốt nhiệm vụ được xác định là thích hợp hơn.

0

Nó thường được coi là thực hành tốt để có sự gắn kết cao với khớp nối thấp. Dưới đây là một vài liên kết liên quan đến khớp nối và sự gắn kết.

khớp nối wikipedia

sự gắn kết wikipedia

khớp nối thấp, độ kết dính cao

stackoverflow về thực hành tốt nhất

Có hai lớp tham chiếu lẫn nhau là có khớp nối cao. Các google khuôn khổ Guice mục tiêu để đạt được sự gắn kết cao với khớp nối thấp thông qua phương tiện dependency injection. Tôi sẽ đề nghị bạn đọc thêm về chủ đề này một chút và sau đó thực hiện cuộc gọi của riêng bạn với bối cảnh của bạn.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.