낮은 커플 링 및 밀착력


11

물론 상황에 따라 다릅니다. 그러나 하위 레버 오브젝트 또는 시스템이 상위 레벨 시스템과 통신 할 때 상위 오브젝트에 대한 포인터를 유지하기 위해 콜백 또는 이벤트를 선호해야합니까?

예를 들어, world멤버 변수 가있는 클래스가 있습니다 vector<monster> monsters. monster클래스가와 통신 할 때 world콜백 함수를 선호 합니까? 아니면 world클래스 내부의 monster클래스에 대한 포인터가 있어야 합니까?


질문 제목의 철자를 제외하고 실제로 질문 형식으로 표현되지는 않습니다. 이 질문을 현재 형식으로 유용한 답변을 얻을 수 없다고 생각하기 때문에이 문구를 다시 말하면 여기에서 요청한 내용을 확정하는 데 도움이 될 것입니다. 또한 게임 디자인 문제가 아니며 프로그래밍 구조에 대한 질문이기도합니다 (디자인 태그가 적절하지 않은지 확실하지 않다면 '소프트웨어 디자인'과 태그에서 어디로 내려 왔는지 기억할 수 없습니다)
MrCranky

답변:


10

한 클래스가 다른 클래스와 밀접하게 연결되지 않고 대화 할 수있는 세 가지 주요 방법이 있습니다.

  1. 콜백 함수를 통해.
  2. 이벤트 시스템을 통해.
  3. 인터페이스를 통해.

세 사람은 서로 밀접하게 관련되어 있습니다. 여러 가지 방법으로 이벤트 시스템은 단지 콜백 목록입니다. 콜백은 하나의 메소드를 가진 인터페이스입니다.

C ++에서는 콜백을 거의 사용하지 않습니다.

  1. C ++은 this포인터 를 유지하는 콜백을 잘 지원하지 않으므로 객체 지향 코드에서 콜백을 사용하기가 어렵습니다.

  2. 콜백은 기본적으로 확장 할 수없는 단일 메소드 인터페이스입니다. 시간이 지남에 따라 거의 항상 인터페이스를 정의하는 데 하나 이상의 메소드가 필요하고 단일 콜백으로는 거의 충분하지 않습니다.

이 경우에는 아마도 인터페이스를 할 것입니다. 귀하의 질문에 실제로 귀하는 실제로 monster의사 소통 해야하는 것을 철자하지 않습니다 world. 추측을하면 다음과 같은 작업을 수행합니다.

class IWorld {
public:
  virtual Monster* getNearbyMonster(const Position & position) = 0;
  virtual Item*    getItemAt(const Position & position) = 0;
};

class Monster {
public:
  void update(IWorld * world) {
    // Do stuff...
  }
}

class World : public IWorld {
public:
  virtual Monster* getNearbyMonster(const Position & position) {
    // ...
  }

  virtual Item*    getItemAt(const Position & position) {
    // ...
  }

  // Lots of other stuff that Monster should not have access to...
}

여기서 아이디어는 액세스해야 할 IWorld최소한의 값만 입력 하는 Monster것입니다. 세상에 대한 견해는 가능한 좁아 야합니다.


1
+1 대의원 (콜백)은 보통 시간이 지남에 따라 더 많아집니다. 몬스터가 물건을 얻을 수 있도록 인터페이스를 제공하는 것이 제 생각에는 좋은 방법입니다.
Michael Coleman

12

콜백 함수를 사용하여 호출중인 함수를 숨기지 마십시오. 콜백 함수를 넣고 해당 콜백 함수에 하나의 함수 만 할당 된 경우 실제로 커플 링을 중단하지는 않습니다. 다른 추상화 계층으로 마스킹하고 있습니다. 컴파일 시간 외에는 아무것도 얻지 못하지만 선명도가 떨어집니다.

나는 그것을 최선의 방법이라고 부르지 않을 것이지만, 부모에 대한 포인터를 갖기 위해 포함 된 엔티티를 갖는 것이 일반적인 패턴입니다.

즉, 인터페이스 패턴을 사용하여 몬스터에게 전 세계에서 호출 할 수있는 기능의 제한된 하위 세트를 제공하는 것이 좋습니다.


+1 몬스터에게 부모를 부르는 제한된 방법을주는 것은 제 생각에는 좋은 중간 방법입니다.
Michael Coleman

7

일반적으로 양방향 링크를 시도하고 피하려고하지만 링크가 있어야하는 경우이를 만드는 방법과 끊는 방법이 있으므로 절대로 불일치가 발생하지 않도록해야합니다.

종종 필요에 따라 데이터를 전달하여 양방향 링크를 완전히 피할 수 있습니다. 사소한 리팩토링은 몬스터가 월드와의 연결을 유지하는 대신, 필요한 몬스터 방법을 참조하여 월드를 전달하도록하는 것입니다. 몬스터가 엄격히 요구하는 세계의 비트에 대한 인터페이스 만 전달하는 것이 더 좋습니다. 즉, 몬스터가 세계의 구체적인 구현에 의존하지 않습니다. 이것은 인터페이스 분리 원리종속성 반전 원리 에 해당하지만 때로는 이벤트, 신호 + 슬롯 등으로 얻을 수있는 초과 추상화를 시작하지 않습니다.

어떤 식 으로든 콜백을 사용하는 것이 매우 전문적인 미니 인터페이스라고 할 수 있습니다. 인터페이스 객체의 메소드 모음 하나 또는 다른 콜백의 여러 메소드 모음을 통해 목표를보다 의미있게 달성 할 수 있는지 여부를 결정해야합니다.


3

컨테이너가 컨테이너를 호출하는 것을 피하려고합니다. 혼동을 일으키고 정당화하기가 너무 쉬워지고 남용되어 관리 할 수없는 종속성을 만듭니다.

제 생각에 이상적인 솔루션은 상위 수준의 클래스가 하위 수준의 클래스를 관리하기에 충분히 똑똑하다는 것입니다. 예를 들어, 괴물과 기사 사이의 충돌이 다른쪽에 대해 알지 못하고 발생했는지 여부를 아는 세계는 기사와 충돌했는지 세계에 묻는 괴물보다 나보다 낫습니다.

귀하의 경우 또 다른 옵션으로, 아마도 괴물 클래스가 왜 월드 클래스에 대해 알아야하는지 알아낼 것입니다. 세계 클래스에 무언가 자신의 클래스로 나눌 수있는 무언가가 있음을 알게 될 것입니다 몬스터 클래스가 알아야 할 감각.


2

이벤트 없이는 멀지 않을 것입니다. 그러나 이벤트 시스템을 작성 (및 디자인)하기 전에 실제 질문을해야합니다. 몬스터가 왜 세계적 수준과 소통할까요? 정말 그래?

플레이어를 공격하는 몬스터 인 "고전적인"상황을 봅시다.

몬스터가 공격하고 있음 : 세상은 영웅이 몬스터 옆에있는 상황을 잘 식별하고 몬스터에게 공격하도록 지시 할 수 있습니다. 몬스터의 기능은 다음과 같습니다.

void Monster::attack(LivingCreature l)
{
  // Call to combat system
}

그러나 이미 몬스터를 알고있는 세계는 몬스터가 알 필요가 없습니다. 실제로 몬스터는 월드 클래스의 존재를 무시할 수 있습니다.

괴물이 움직일 때도 마찬가지입니다 (서브 시스템이 생물을 가져 와서 이동 계산 / 의도를 처리하게합니다. 괴물은 단지 데이터 가방 일 뿐이지 만 많은 사람들이 이것이 실제 OOP가 아니라고 말할 것입니다).

내 요점은 : 물론 이벤트 (또는 콜백)는 훌륭하지만 직면 할 모든 문제에 대한 유일한 대답은 아닙니다.


1

가능할 때마다 개체 간 통신을 요청 및 응답 모델로 제한하려고합니다. 내 프로그램에서 두 객체 A와 B 사이에 A가 B의 메소드를 직접 또는 간접적으로 호출하거나 B가 A의 메소드를 직접 또는 간접적으로 호출하는 방법이있을 수 있도록 부분적 순서가 내재되어 있습니다. 그러나 A와 B가 서로의 방법을 상호 호출하는 것은 불가능합니다. 물론, 메소드의 호출자와 역방향 통신을 원할 수도 있습니다. 이 작업을 좋아하는 몇 가지 방법이 있으며 그중 어느 것도 콜백이 아닙니다.

한 가지 방법은 메소드 호출의 리턴 값에 추가 정보를 포함시키는 것입니다. 이는 클라이언트 코드가 프로 시저가 제어를 리턴 한 후 클라이언트 코드가 수행 할 작업을 결정 함을 의미 합니다.

다른 방법은 상호 자식 개체를 호출하는 것입니다. 즉, A가 B에서 메소드를 호출하고 B가 A와 일부 정보를 통신해야하는 경우 B는 C에서 메소드를 호출합니다. 여기서 A와 B는 모두 C를 호출 할 수 있지만 C는 A 또는 B를 호출 할 수 없습니다. B가 제어를 A로 되 돌린 후 C에서 정보를 가져 오는 책임 오브젝트 A는 여전히 리턴 값에서만 정보를 검색 할 수 있습니다. 오브젝트 A의 메소드 중 어느 것도 B 또는 C에 의해 호출되지 않습니다.이 트릭의 변형은 C를 메소드에 매개 변수로 전달하는 것이지만 C와 A와 B의 관계에 대한 제한은 여전히 ​​적용됩니다.

이제 중요한 질문은 내가 이런 식으로 일을 주장하는 이유 입니다. 세 가지 주요 이유가 있습니다.

  • 내 객체가 더 느슨하게 결합되어 있습니다. 내 객체는 다른 객체를 캡슐화 할 수 있지만 호출자의 컨텍스트에 의존하지 않으며 컨텍스트는 캡슐화 된 객체에 의존하지 않습니다.
  • 제어 흐름을 쉽게 추론 할 수 있습니다. self메소드 실행 중 내부 상태를 변경할 수있는 유일한 코드 는 해당 메소드 중 하나이고 다른 메소드는 아니라고 가정 할 수 있습니다 . 이것은 같은 종류의 추론으로 동시 객체에 뮤텍스를 넣을 수 있습니다.
  • 그것은 내 객체의 캡슐화 된 데이터의 불변을 보호합니다. 공용 메소드는 불변에 의존 할 수 있으며, 다른 메소드가 이미 실행중인 동안 한 메소드를 외부에서 호출 할 수있는 경우 이러한 불변은 위반 될 수 있습니다.

모든 콜백 사용에 반대하는 것은 아닙니다. "호출자 호출"에 대한 내 정책에 따라, 객체 A가 B에서 메소드를 호출하고 콜백을 전달하는 경우 콜백은 A의 내부 상태를 변경하지 않을 수 있으며 A와 A로 캡슐화 된 객체를 포함합니다. A의 맥락에서 객체. 다시 말해, 콜백은 B에 의해 주어진 객체에 대해서만 메소드를 호출 할 수 있습니다. 실제로 콜백은 B와 동일한 제한을받습니다.

마지막으로 느슨한 결말은 내가 말한이 부분 순서에 관계없이 순수한 기능을 호출 할 수 있다는 것입니다. 순수한 함수는 변경 가능한 상태 또는 부작용에 따라 변하거나 의존 할 수 없다는 점에서 메소드와 약간 다르므로 혼동되는 문제에 대해 걱정할 필요가 없습니다.


0

몸소? 나는 단지 싱글 톤을 사용합니다.

예, 디자인이 좋지 않고 객체 지향이 아닙니다. 무엇을 알고 있습니까? 상관 없어요 . 저는 기술 쇼케이스가 아닌 게임을 만들고 있습니다. 아무도 나를 코드에서 채점하지 않을 것입니다. 목적은 재미있는 게임을 만드는 것이며, 내 방식으로 얻는 모든 것이 덜 재미있는 게임이 될 것입니다.

한 번에 두 개의 세계가 실행됩니까? 아마도! 아마 당신은 할 것입니다. 그러나 지금 당장 그 상황을 생각할 수 없다면 아마 그렇지 않을 것입니다.

그래서 내 해결책 : 세계 싱글 톤을 만드십시오. 함수를 호출하십시오. 엉망진창으로 끝내십시오. 당신은 수있는 곳이 리드의 실수를하고, 할 - 모든 단일 함수에 추가 매개 변수를 전달하지 않습니다. 또는 작동하는 코드를 작성할 수도 있습니다.

이 방법을 사용하면 지저분해질 때 ( "if"가 아니라 "언제나") 정리할 약간의 규율이 필요하지만 코드가 지저분 해지는 것을 막을 방법이 없습니다. 스파게티 문제가 있거나 수천 -절제 층 문제. 적어도 이런 식으로 많은 양의 불필요한 코드를 작성하지 않습니다.

그리고 더 이상 싱글 톤을 원하지 않는다면, 그것을 제거하는 것이 일반적으로 매우 간단합니다. 약간의 작업을 수행하고 수십억 개의 매개 변수를 전달하지만 그 매개 변수는 어쨌든 전달해야합니다.


2
아마 좀 더 간결하게 표현할 것이다. "리팩터링을 두려워하지 말라".
Tetrad

싱글 톤은 악하다! 세계는 훨씬 낫다. 귀하가 기재 한 모든 싱글 톤 미덕은 전 세계적으로 동일합니다. 다양한 하위 시스템에 대한 전역 포인터 (실제로 전역 함수가 참조를 반환)를 사용하여 기본 함수에서 초기화 / 파괴합니다. 전역 포인터는 초기화 순서 문제를 피하고, 분해하는 동안 싱글 톤을 매달거나, 싱글 톤을 간단하게 구성하지 않습니다. 싱글 톤을 반복하는 것은 악합니다.
deft_code

@Tetrad, 매우 동의했습니다. 당신이 가질 수있는 최고의 기술 중 하나입니다.
ZorbaTHut
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.