순환 클래스 의존성


12

서로를 필요로하는 2 개의 수업을 갖는 것이 나쁜 디자인입니까?

나는 GameEngine몇 가지 GameState물건 을 가진 수업이 있는 작은 게임을 작성하고 있습니다. 여러 렌더링 방법에 액세스하려면 이러한 GameState객체도 GameEngine클래스 를 알아야합니다 . 따라서 순환 종속성입니다.

이 나쁜 디자인이라고 부를까요? 나는 확실히 확신하지 못하기 때문에 묻는데, 현재도 여전히 이러한 것들을 리팩토링 할 수 있습니다.


2
대답이 '예'라면 매우 놀랐습니다.
doppelgreener

논리적으로 분리 된 두 가지 질문이 있기 때문에 그 중 하나에 대한 대답은 '예'입니다. 실제로 무언가를하는 상태를 설계하고 싶은 이유는 무엇입니까? 상태를 확인하고 작업을 수행 할 관리자 / 컨트롤러가 있어야합니다. 상태 유형의 객체가 다른 책임을 맡게하는 것은 약간 혼란 스럽습니다. 당신이 그것을하고 싶다면, C ++는 당신의 친구 입니다.
teodron

내 GameState 클래스는 인트로, 실제 게임, 메뉴 등과 같은 것입니다. GameEngine은 이러한 상태를 스택에 저장하므로 상태를 일시 중지하고 메뉴를 열거 나 컷씬을 재생할 수 있습니다. Engine이 창을 만들고 렌더링 컨텍스트를 가지고 있기 때문에 GameState 클래스는 GameEngine을 알아야합니다. .
shad0w

5
이 모든 규칙은 빠른 목표를 명심하십시오. 빠른 실행, 빠른 생성, 빠른 유지 관리. 때로는 이러한 패싯이 서로 상충되기 때문에 진행 방법을 결정하려면 비용-이익 분석을 수행해야합니다. 그렇게 많이 생각하고 직감을 불러도 개발자의 90 % 이상이 여전히 잘하고 있습니다. 당신이 잘못하면 당신을 죽일 숨겨진 괴물은 없습니다. 그는 숨겨진 유료 부스 연산자입니다.
DampeS8N

답변:


0

본질적으로 나쁜 디자인은 아니지만 쉽게 제어 할 수 없습니다. 실제로 일상적인 코드에서 해당 디자인을 사용하고 있습니다. 예를 들어 vector는 첫 번째 반복자임을 알고 반복자는 컨테이너에 대한 포인터를 가지고 있습니다.

이제 귀하의 경우 GameEnigne 및 GameState에 대해 두 개의 개별 클래스를 갖는 것이 훨씬 좋습니다. 기본적으로이 두 가지가 서로 다른 작업을 수행하므로 나중에 GameState를 상속하는 많은 클래스를 정의 할 수 있습니다 (게임의 각 장면에 대한 GameState 등). 그리고 아무도 서로에게 접근 할 필요성을 부인할 수 없습니다. 기본적으로 GameEngine은 게임 상태를 실행 중이므로 이에 대한 포인터가 있어야합니다. 그리고 GameState는 GameEngine에 정의 된 리소스 (렌더링, 물리 관리자 등)를 사용하고 있습니다.

이 두 클래스를 본질적으로 다른 일을하고 있기 때문에 서로 결합 할 수 없으며 결합해서는 안됩니다. 결합하면 아무도 좋아하지 않는 매우 큰 클래스가됩니다.

지금까지 우리는 디자인에 순환 의존성이 필요하다는 것을 알고 있습니다. 안전하게 만드는 방법에는 여러 가지가 있습니다.

  1. 우리가 제안한 것처럼 각 포인터를 다른 포인터에 넣을 수 있습니다. 가장 간단한 솔루션이지만 쉽게 제어 할 수 없습니다.
  2. 싱글 톤 / 멀티 톤 디자인을 사용할 수도 있습니다.이 방법을 사용하면 GameEngine 클래스를 싱글 톤으로 정의하고 각 해당 GameState를 멀티 톤으로 정의해야합니다. 많은 개발자들이 singleton과 multiton을 모두 AntiPattern으로 생각하지만, 나는 이런 종류의 디자인을 선호합니다.
  3. 전역 변수를 사용할 수 있습니다. 기본적으로 Singleton / multiton과 동일하지만 프로그래머가 마음대로 인스턴스를 만들 수 없도록 제한한다는 점에서 약간의 차이가 있습니다.

그의 대답을 마치려면이 세 가지 방법 중 하나를 사용할 수 있지만 모든 디자인이 실제로 위험하고 쉽게 유지 관리 할 수없는 코드를 생성 할 수 있으므로 두 가지 방법 중 하나를 과도하게 사용하지 않도록주의해야합니다.


6

서로를 필요로하는 2 개의 수업을 갖는 것이 나쁜 디자인입니까?

약간의 Code Smell 이지만, 그대로 둘 수 있습니다. 이것이 더 쉽고 빠른 게임 시작 및 실행 방법이라면 사용하십시오. 그러나 어느 시점에서 리팩토링해야 할 가능성이 높기 때문에 명심하십시오.

C ++의 경우 순환 종속성은 그렇게 쉽게 컴파일되지 않으므로 컴파일을 수정하는 데 시간을 소비하는 대신 제거하는 것이 좋습니다.

좀 더 의견 이 있으시면 SO에 대한 이 질문을 참조하십시오 .

[나의 디자인]을 나쁜 디자인이라고 부릅니까?

아니요, 모든 것을 한 클래스에 넣는 것보다 낫습니다.

그렇게 크지는 않지만 실제로 본 대부분의 구현에 매우 가깝습니다. 일반적으로 게임 상태에 대한 관리자 클래스 ( 주의! )와 렌더러 클래스가 있으며, 싱글 톤 인 것이 일반적입니다. 따라서 순환 종속성은 "숨겨져"있지만 잠재적으로 존재합니다.

또한 의견에서 알 수 있듯이 게임 상태 클래스가 일종의 렌더링을 수행하는 것은 약간 이상합니다. 상태 정보 만 보유해야하며 렌더링은 렌더러 또는 게임 객체 자체의 일부 그래픽 구성 요소에 의해 처리되어야합니다.

이제 최고의 디자인 이있을 수 있습니다 . 다른 답변이 좋은 아이디어를 가져 왔는지 궁금합니다. 아직도, 당신은 아마 당신의 게임에 가장 적합한 디자인을 찾을 수있는 사람 일 것입니다.


왜 코드 냄새일까요? 부모-자식 관계가있는 대부분의 개체는 서로를 볼 필요가 있습니다.
Kikaimaru

4
어떤 클래스가 무엇을 담당하는지 정의 하지 않는 가장 쉬운 방법이기 때문 입니다. 두 개의 클래스로 쉽게 연결되어 두 코드 중 하나에서 새 코드를 추가 할 수 있으므로 더 이상 개념적으로 분리되지 않습니다. 그것은 또한 스파게티 코드의 열린 문입니다. 클래스 A는 이것을 위해 클래스 B를 호출하지만 B는 A의 정보 등을 필요로합니다. 따라서 아니요, 자식 개체가 부모에 대해 알아야한다고 말하지 않습니다. 가능하다면 그렇지 않은 것이 좋습니다.
Laurent Couvidou

미끄러운 경사 오류입니다. 정적 클래스도 나쁜 것으로 이어질 수 있지만 유틸리티 클래스는 코드 냄새가 아닙니다. 그리고 Scene에 SceneNode가 있고 SceneNode가 Scene을 참조하는 것과 같은 "좋은"방법이 있다는 것을 정말로 의심합니다. 따라서 두 개의 다른 장면에 추가 할 수 없습니다. 그리고 어디에서 멈추겠습니까? A는 B, B는 C를, C는 A는 코드 냄새가 필요합니까?
Kikaimaru

실제로 이것은 정적 클래스와 같습니다. 주의해서 다루어야하며 필요한 경우에만 사용하십시오. 지금 나는 경험을 통해 이야기하고 있으며 , 이런 식으로 생각하는 유일한 사람은 아니지만 실제로 이것은 의견의 문제입니다. 내 의견은 OP가 제공 한 맥락에서 실제로 냄새가 난다는 것입니다.
Laurent Couvidou

그 위키 백과 기사에는 그 사건에 대한 언급이 없습니다. "부적절한 친밀감"의 사례는 실제로 해당 클래스를 볼 때까지 말할 수 없습니다.
Kikaimaru

6

서로를 직접 참조하는 2 개의 클래스가 있어야하는 것은 종종 나쁜 디자인으로 간주됩니다. 실제로는 코드를 통한 제어 흐름을 따르는 것이 더 어려울 수 있으며, 개체의 소유권과 수명이 복잡 할 수 있습니다. 즉, 클래스가 다른 클래스없이 재사용 가능하지 않다는 것을 의미 할 수 있습니다. 이러한 클래스 중 세 번째 '중재자'클래스 등이 있습니다.

그러나 두 객체 가 서로를 참조하는 것은 매우 일반적이며 여기서 차이점은 일반적으로 한 방향의 관계가 더 추상적이라는 것입니다. 예를 들어 Model-View-Controller 시스템에서 View 객체는 Model 객체에 대한 참조를 보유 할 수 있으며 모든 객체에 대해 알 수 있으므로 모든 메소드와 속성에 액세스 할 수있어 View가 관련 데이터로 채워질 수 있습니다. . Model 객체는 자체 데이터가 변경되었을 때 View를 업데이트 할 수 있도록 View에 대한 참조를 다시 보유 할 수 있습니다. 그러나 모델이 View에 의존하게 만드는 View 참조가있는 모델 대신 일반적으로 View는 Observable 인터페이스를 구현합니다.Update()함수가 있고 모델은보기 일 수있는 Observable 객체에 대한 참조를 보유합니다. 모델이 변경되면 Update()모든 Observable Update()을 호출 하고 View 는 모델을 다시 호출하고 업데이트 된 정보를 검색하여 구현 합니다. 여기서 이점은 모델이 뷰에 대해 전혀 알지 못하며 뷰가없는 다른 상황에서도 재사용 할 수 있다는 것입니다.

게임에서 비슷한 상황이 있습니다. GameEngine은 일반적으로 GameStates에 대해 알고 있습니다. 그러나 GameState는 GameEngine에 대한 모든 정보를 알 필요는 없습니다. GameEngine의 특정 렌더링 방법에만 액세스하면됩니다. (a) GameEngine이 한 클래스 내에서 너무 많은 일을하려고하거나, (b) GameState가 전체 게임 엔진을 필요로하지 않으며 렌더링 가능한 부분 만 필요하다는 경고가 머리에 약간 울립니다.

이를 해결하기위한 세 가지 접근 방식은 다음과 같습니다.

  • GameEngine을 Renderable 인터페이스에서 파생시키고 해당 참조를 GameState에 전달하십시오. 렌더링 부분을 리팩토링 한 경우 동일한 인터페이스를 유지해야합니다.
  • 렌더링 부분을 새로운 Renderer 객체로 분해하고 대신 GameStates에 전달하십시오.
  • 그대로 두십시오. 어쩌면 언젠가 GameState에서 모든 GameEngine 기능에 액세스하고 싶을 것입니다. 그러나 유지 관리가 쉽고 확장하기 쉬운 소프트웨어는 일반적으로 각 클래스가 가능한 한 적은 양의 외부 를 참조해야하므로 단일 객체를 잘 수행하는 하위 객체와 인터페이스로 분류하는 것이 좋습니다. 정의 된 작업 이 바람직합니다.

0

낮은 커플 링과 높은 응집력을 갖는 것이 일반적으로 좋은 습관으로 인식된다. 커플 링과 응집력에 관한 몇 가지 링크가 있습니다.

위키 백과 커플 링

위키 백과 응집력

낮은 커플 링, 높은 응집력

모범 사례의 스택 오버플로

두 클래스가 서로 참조하면 높은 커플 링이 있습니다. 구글 guice 골격 목적 의존성 주입 수단을 통해 낮은 결합 높은 응집력을 달성했다. 나는 당신이 주제를 조금 더 읽고 당신의 상황에 따라 당신의 자신의 전화를 걸 것을 제안합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.