약한 결합 방식 : 델리게이트
결합은 종류가 2가지 있다.
- 강한 결합
- 클래스들이 서로 의존성을 가짐 = SOLID중 DIP 원칙에서 벗어남
- 약한 결합
- 실존 클래스끼리가 아닌 추상적 설계에 의존 = DIP원칙
- Person → [출입] ← 카드로 엮인다고 생각 (여기서 출입이 추상적 설계에 해당)
- 우린 여기서 출입을 인터페이스로 구현할 수도 있다.
- 다만, 카드의 쓰임이 출입 말고도 인증, 결제등등 더 많은 기능이 추가될때마다 인퍼페이스를 만들기엔 귀찮다.(클래스 선언을 죄다 해줘야하니까)
- 그래서 등장한게 델리게이트 : 함수형 객체 (기능을 변수로 지정하고, 변수가 함수처럼 실행되는 구조)
언리얼 델리게이트
Delegate (델리게이트)로 C++ 오브젝트 상의 멤버 함수 호출을 일반적이고 유형적으로 안전한 방식으로 할 수 있습니다.
여기서 안전한 방식이라는 것은 C의 함수 포인터(CallBack함수)보다 안정화 되어있음을 말한다.
(델리게이트도 항상 참조로 전달, C++은 델리게이트가 없다..)
델리게이트를 사용하기 위해서는
- 함수 시그니처 : 원하는 기능을 가진 함수
- 선언 매크로 : 원하는 기능을 가진 함수를 델리게이트 선언
- 바인딩함수 : 델리게이트를 원하는 오브젝트와 바인딩(약한 결합)
- 델리게이트 실행 함수 : 델리게이트(원하는 기능을 가진 함수)를 실행하는 함수
- 페이로드 데이터 : 델리게이트를 바인딩할 때(함수를 오브젝트에 연결할 때) 전달하는 임의의 변수
→ 그냥 묶는 객체에 대한 정보를 하나의 구문으로 나타내는 것 (함수명, 인자를 포함)
델리게이트 동작 방법 : 발행 구독 디자인 패턴
시스템 객체에는 발행자(Publisher, 네이버)와 구독자(Subscriber)가 있다.
각각 웹툰 작가, 네이버, 구독자는 각자 할일만 하므로 다른 일에 신경쓰지 않고 효율적이다. : 이게 발행구독패턴의 장점
- 제작자(웹툰 작가, 기능을 하는 함수)와 구독자는 발행자(네이버)를 통해 연결되어 약한 결합으로 구성
- 유지보수가 쉽고, 유연하게 활동되며, 테스트가 쉬워진다.
- 시스템 스케일을 유연하게 조절할 수 있으며, 기능 확장이 용이하다.
델리게이트 실습
상황
- 학사정보(CourseInfo)와 3명의 학생(Student)이 있다.
- 시스템에서 학사정보를 변경한다.
- 학사정보가 변경되면 알림 구독(Student → 델리게이트, 구독 Add)한 학생들에게 변경 내용을 자동으로 전달(델리게이트 → Student, 방송 Broadcast)한다.
언리얼 델리게이트 선언 시 고려사항
- 어떤 데이터를 전달하고 받을래?
- 몇 개의 인자
- 각각의 인자의 타입
- 어떤 방식으로 전달(1:1, 1:다) MULTICAST 사용 유무 결정
- 프로그래밍 환경 설정은? DYNAMIC 사용 유무 결정
- C++만 이용
- UFUNCTION으로 지정된 블루프린트 함수 사용
- 어떤 함수와 연결?
- 클래스 외부에 설계된 C++ 함수와 연결
- 전역에 설계된 정적 함수와 연결
- 언리얼 오브젝트의 멤버 함수와 연결 (대다수 사용)
이 고려사항을 기준으로 매크로를 정하게 된다.
기본적으로
DECLARE_{델리게이트유형}DELEGATE{함수정보}
에서 델리게이트 유형과 함수정보만 지정한다.
델리게이트 유형
- 일대일 형태, C++ 만 지원한다면, DECLARE_DELEGATE
- 일대다 형태, C++ 만 지원한다면, DECLARE_MULTICAST_DELEGATE
- 일대일 형태 + 블루프린트 = DECLARE_DYNAMIC
- 일대다 형태 + 블루프린트 = DECLARE_DYNAMIC_MULTICAST
함수 정보
- 인자X 반환X
void() = 공란으로 둔다. : DECLARE_DELEGATE - 인자 1개 반환값X
void (int num) = OneParam으로 지정 : DECLARE_DELEGATE_OneParam - 인자 3개 반환값O (1:1이고 세개의 인자를 가지며 값을 반환하는 함수)
RetVal_ThreeParams로 지정 : DECLARE_DELEGATE_RetVal_ThreeParams
리턴밸류의 경우에는 DYNAMIC만 RetVal을 선언할 수 있음.
실습
상황에 의해서
1. 알림 주체에 대한 정보(학사정보)와 알림내용(변경된 내용) 2가지 인자를 가진다.
2. 변경된 학사 정보는 다수 인원을 대상으로 발송 : MULTICAST
3. 오직 C++만 사용 : DYNAMIC 사용X
∴ DELEGATE_MULTICAST_DELEGATE_TwoParams 사용
1. 학사정보와 델리게이트 구현
- 가장 먼저 DELEGATE_MULTICAST_DELEGATE_TwoParams(델리게이트 이름, 인자, 인자) 매크로를 지정한다.
- 델리게이트 이름은 F로 시작하고, 마지막에 Signature을 붙여준다.
- 20줄 생성자
- 22줄 FCourseInfoOnChangedSignature OnChanged;
: 델리게이트 정보를 객체로 선언 - 24줄 void ChangeCourseInfo(const FString& InSchoolName, const FString& InNewContents);
: 외부에서 델리게이트를 호출할 함수(주석에는 학사정보를 변경할 때 사용하는 함수라고 적었는데?)
: 정확히 저 함수는 MyGameInstance에서 학사 정보가 변경되었을 때 호출되는 함수이다.
- ChangeCourseInfo라는 함수 자체가 델리게이트(OnChanged)를 Broadcast해주는 기능이 있다.
Broadcast를 해주면 구독하고 있는 객체(학생)가 변경 사항을 알게 된다.
: 델리게이트 → Student 발행하는 과정
2. 학생 구현
- 학생쪽에서 변경사항을 알아챘어, 그럼 알아챘다는 반응을 해줄 함수 GetNotification을 작성해준다.
- 별거 없다. 그냥 broadcast를 받으면 구독중(add) 상태를 나타내기 위한 출력문이다.
3. 결과 출력MyGameInstance
2번까지만 하면 일단은 발행구독 디자인 패턴을 사용한 델리게이트 구현이 끝난다. 하지만? 이게 제대로 되었는지 파악을 할 수 없다. 그래서 학사정보와 학교를 다같이 감싸는 객체인 MyGameInstance를 통해 출력해본다.
- 델리게이트를 가진 CourseInfo도 어쨌든 오브젝트이므로 MyGameInstance에서 사용하려면 헤더파일에서 참조를 해야한다.
다만, 헤더를 추가하면? 의존성이 높아진다!!!! 그래서 뭐써지? TObjectPtr
이 전방선언을 통해 포인터를 가지게 만든다.
- 21줄 같은 경우 원래는 CreateDefaultSubobject()을 사용해서 서브 오브젝트를 만들었지만,(1번방법)
이번에는 런타임에서 언리얼오브젝트를 생성하는 NewObject를 사용했다. → 컴포지션 구현 - 33줄 CourseInfo의 OnChanged(델리게이트)를 부르는데, AddUObject를 사용해서 학생 오브젝트(인스턴스)와 실행할 멤버 함수를 묶을 수 있다. : Student → 델리게이트 구독하는 과정
- 37줄 정보 변경 : CourseInfo의 함수 ChangeCourseInfo를 통해 정보를 변경하면,
이렇게 뜬다.
TObjectPtr, 컴포지션 구현 방법 참고
델리게이트 정리
중요한 점
지금까지 하면서 Student와 CourseInfo사이에는 그 어떤 참조도 포함되지 않았다.
전체적인 순서를 보여주면
- 구독 : 일단 MyGameInstance에서 Student가 CourseInfo의 델리게이트 OnChanged를 사용해서 구독한다.
AddUObject를 사용해서 구독할 객체(Student)와 발행받았을 때 실행할 함수(GetNotification)를 한번에 묶을 수 있다. - event : CourseInfo 오브젝트의 정보를 변경한다. (여기선 학사정보를 변경)
그러기 위해서 CourseInfo의 함수 ChangeCourceInfo를 사용한다. - 발행 : MyGameInstance에서 사용한 함수 ChangeCourceInfo가 발동되면, 그 함수 안에 있던 델리게이트 OnChanged의 함수 Broadcast()라는 함수가 발동되어 구독되어있는 모든 객체(여기선 Student)에게 발행된다.(말이 발행이지 그냥 알림가는거라고 생각하면쉽다.) 발행받은 객체들은 구독 정보(Student, GetNotification)에 의해 실행된다.
다시 반복하지만, 이 과정에서 CourseInfo와 Student사이에는 아무런 접점이 없었다.
접점 = 참조, 헤더
CourseInfo의 델리게이트 OnChanged와 Student만 연결되었을 뿐
이렇게 사용하면, CourseInfo나 Student를 삭제, 확장, 변경하더라도 MyInstance에서만 수정해주면 되므로
타 모듈에 영향을 주지 않고 동작할 수 있다.
'하고싶은거 > Unreal' 카테고리의 다른 글
Part1 11. 언리얼 컨테이너 라이브러리 UCL : UStruct, TMap (0) | 2024.03.11 |
---|---|
Part1 10. 언리얼 컨테이너 라이브러리 UCL : TArray, TSet (1) | 2024.03.11 |
Part1 08. 언리얼 C++ 설계 : 컴포지션 (0) | 2024.03.10 |
Part1 07. 언리얼 C++ 설계 : 인터페이스 (0) | 2024.03.09 |
Part1 05.06. 언리얼 리플렉션 시스템 (프로퍼티 시스템) (0) | 2024.03.08 |
#개발 #게임 #일상
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!