#include "MyGameInstance.h"
#include "Card.h"
#include "CourseInfo.h"
#include "Staff.h"
#include "Student.h"
#include "Teacher.h"
UMyGameInstance::UMyGameInstance()
{
SchoolName = TEXT("학교");
}
void UMyGameInstance::Init()
{
Super::Init();
// CDO가 아닌 런타임에 합성으록 가져감
// CourseInfo가 서브 오브젝트로 등록되기 위해 Outer로 this를 전달 즉, GameInstance의 서브 오브젝트로 등록
// 이렇게 코드로 컴포지션 관계를 만들 수 있는데, 이는 CDO가 아닌 런타임에 할당 하는 것이다.
// 좀 더 나가아서 this가 아닌 다른 오브젝트의 서브 오브젝트로 등록할 수도 있다.
CourseInfo = NewObject<UCourseInfo>(this);
UE_LOG(LogTemp, Log, TEXT("======================"));
// but 이 오브젝트는 지역 변수로 선언되어 함수가 끝나면 메모리에서 해제되기에 outer를 등록할 필요가 없다.
UStudent* Student1 = NewObject<UStudent>();
Student1->SetName(TEXT("학생1"));
UStudent* Student2 = NewObject<UStudent>();
Student2->SetName(TEXT("학생2"));
UStudent* Student3 = NewObject<UStudent>();
Student3->SetName(TEXT("학생3"));
// 가지고 있는 학사 정보 시스템의 델리게이트에 학생과 델리게이트와 같은 형식을 가진 함수를 바인딩
CourseInfo->OnChanged.AddUObject(Student1, &UStudent::GetNotification);
CourseInfo->OnChanged.AddUObject(Student2, &UStudent::GetNotification);
CourseInfo->OnChanged.AddUObject(Student3, &UStudent::GetNotification);
CourseInfo->OnChanged.AddLambda([](const FString& School, const FString& NewCourseInfo)
{
UE_LOG(LogTemp, Log, TEXT("[Lambda] 학사 정보가 변경되었습니다. 학교: %s, 정보: %s"), *School, *NewCourseInfo);
});
// 학사 정보를 변경 내부에서 Invoke
CourseInfo->ChangeCourseInfo(SchoolName, TEXT("변경된 학사 정보"));
UE_LOG(LogTemp, Log, TEXT("======================"));
}
DECLARE_MULTICAST_DELEGATE_TwoParams를 통해 두 개의 인자를 가지는 멀티캐스트 델리게이트를 선언
9강: 언리얼 C++ 설계 III - 델리게이트
느슨한 결합(Loose Coupling)
ICheck
인터페이스를 상속받은 새로운 카드 인터페이스를 선언하여 해결한다.느슨한 결합의 간편한 구현 - 델리게이트(Delegate)
하지만 위 방식은 매번 인터페이스를 만들어야 하기에 번거로울 수 있다. 따라서 함수를 오브젝트와 같이 관리한다면 더욱 편리하게 느슨한 결합을 구현할 수 있다.
C#
의 유용한 델리게이트를 따라 언리얼 C++도 델리게이트를 지원한다.발행 구독 디자인 패턴
델리게이트를 좀 더 깊게 이해하기 위해선
발행 구독 디자인 패턴
을 이해하면 좋다.발행 구독 디자인 패턴은 푸시형태의 알림을 구현하는데 적합한 디자인 패턴으로 발행자(Publisher)와 구독자(Subscriber)로 나뉜다.
장점으로는 제작자와 구독자는 서로를 모르기 때문에 느슨한 결합으로 구성된다. 유지 보수가 쉽고, 유연하게 활용할 수 있으며 테스트가 쉬워진다. 시스템 스케일을 유연하게 조절할 수 있으며, 기능 확장에 용이하다.
옵저버 패턴(Observer Pattern)
예제를 위한 클래스 다이어그램과 시나리오
이번 예제는 학교에서 진행하는 온라인 수업을 예시로 학사 정보와 학생의 관계에 집중한다.
언리얼 델리게이트
언리얼 엔진은 발행 구독 패턴 구현을 위해 델리게이트 기능을 제공한다. 델리게이트의 사전적 의미는 대리자로 학사정보의 구독과 알림을 대리해주는 객체를 의미한다.
언리얼 델리게이트의 선언
언리얼 델리게이트 선언시 고려사항
델리게이트를 설계하기 위해선 많은 사항을 고려해야 한다.
C++
프로그래밍에서만 사용할 것인가?UFUNCTION
으로 지정된 블루프린트 함수와 사용할 것인가?C++
함수와 연결언리얼 델리게이트 선언 매크로
DECLARE_DELEGATE
MULTICAST
로 지정한다.DECLARE_MULTICAST_DELEGATE
DYNAMIC
으로 지정한다.DECLARE_DYNAMIC_DELEGATE
DYNAMIC_MULTICAST
로 지정한다.DECLARE_DYNAMIC_MULTICAST_DELEGATE
DECLARE_DELEGATE
OneParam
으로 지정한다.DECLARE_DELEGATE_OneParam
RetVal_ThreeParams
로 지정한다.DECLARE_DELEGATE_RetVal_ThreeParams
(Multicast
는 반환값을 지원하지 않음)C#, Unity의 Action, Event, Func과 유사하다. 미리 구현해 놓은 델리게이트 유형이 있어서 편리하게 사용할 수 있다.
언리얼 델리게이트 선정 예시
MULTICAST
로 지정DYNAMIC
은 사용하지 않음언리얼 델리게이트의 설계
학사 정보 클래스와 학생 클래스의 상호 의존성을 최대한 없앤다. 즉, 하나의 클래스는 하나의 작업에만 집중하도록 설계한다. (자동적으로 SRP를 준수하게 되면서 리스코프를 제외한 나머지 원칙들도 준수하게 된다.)
학사 정보 클래스는 델리게이트를 선언하고 알림에만 집중하며 학생 클래스는 알림을 수신하는데만 집중한다. 직원도 알림을 받을 수 있도록 유연하게 설계하며, 학사 정보와 학생은 서로 헤더를 참조하지 않도록 신경쓴다.
이를 위해 발행과 구독을 컨트롤하는 주체를 설정한다. 학사 정보에서 선언한 델리게이트를 중심으로 구독과 알림을 컨트롤하는 주체 설정
실습
DECLARE_MULTICAST_DELEGATE_TwoParams
를 통해 두 개의 인자를 가지는 멀티캐스트 델리게이트를 선언DECLARE_MULTICAST_DELEGATE_TwoParams(FCourseInfoOnChangedSignature, const FString&, const FString&);
FCourseInfoOnChangedSignature
는 델리게이트의 이름이다.F
로 접두사를 가지며 뒤로는 클래스 이름과 액션을 붙여서 사용한다.OnChanged
, 마지막으로Signature
를 붙여서 사용한다.const FString&, const FString&
DECLARE_MULTICAST_DELEGATE_TwoParams
이기에 두 개의 인자를 가지는 멀티캐스트 델리게이트를 선언한다.FCourseInfoOnChangedSignature OnChanged;
학사 정보 클래스에 델리게이트 멤버 변수를 선언한다. 이를 통해 구독하므로 이는 발행자가 된다. (unity의 event와 유사)void UCourseInfo::ChangeCourseInfo(const FString& InSchoolName, const FString& InNewContents)
OnChanged.Broadcast(InSchoolName, Contents);
브로드 캐스트 방식으로 등록된 구독자에게 알림을 전달한다.void GetNotification(const FString& School, const FString& NewCourseInfo);
UStudent
클래스도 마찬가지로 대리자와 같은 형식의 함수를 선언하여 알림을 받을 수 있도록 한다.TObjectPtr<class UCourseInfo> CourseInfo;
UCourseInfo
를 가지는 포인터를 선언한다. (UObject
를 상속받은 클래스는 포인터로 선언하는 것이 좋다. 탬플릿)CourseInfo = NewObject<UCourseInfo>(this);
NewObject
의 학습을 위해 런타임에 생성한다.this
를 Outer로 전달하여 GameInstance의 서브 오브젝트로 등록한다.CourseInfo
는UMyGameInstance
의 서브 오브젝트로 등록된다.this
가 아닌 다른 오브젝트의 서브 오브젝트로 등록할 수도 있다. (이건 관리자의 역항인듯)UStudent* Student1 = NewObject<UStudent>();
UObject
객체를 생성한다. 하지만 지역 변수이기에 함수가 끝나면 메모리에서 해제된다. (가비지 컬렉터, 직접 해제해도 된다.) 따라서Outer
를 등록할 필요가 없다.CourseInfo->OnChanged.AddUObject(Student1, &UStudent::GetNotification);
CourseInfo->OnChanged.AddLambda
CourseInfo->ChangeCourseInfo(SchoolName, TEXT("변경된 학사 정보"));
제일 중요한 점은 실제로
Student
와CourseInfo
는 서로 헤더를 참조하지 않는다. 이는 느슨한 결합을 구현한 것이다.정리
데이터 기반의 디자인 패턴을 설계할 때 유용하게 사용된다.