Delegate
class Card
{
public:
Card(int InId) : id(InId) {}
int Id = 0;
};
class Person
{
public:
Person(Card InCard) : ICard(InCard) {}
protected:
Card IdCard;
}
class ICheck
{
public:
virtual bool check() = 0;
};
class Card : public ICheck
{
public:
Card(int InId) : Id(Inid) {}
virtual bool check() { return true;}
private:
int Id = 0;
};
class Person
{
public:
Person(ICheck* InCheck) : Check(InCheck) {}
protected:
ICheck* Check;
};
행동에 대해서 오브젝트로 관리하면 어떨까?
함수를 다루는 방법
- 함수 포인터를 활용한 콜백(callback) 함수의 구현
- 가능은 하나 이를 정의하고 사용하는 과정이 복잡
- 안정성을 스스로 검증
- C++ 17 구약의 std::bind와 std::function 활용은 느림
public class Card
{
public int Id;
public bool CardCheck() {return true;}
};
public delegate bool CheckDelegate();
public class Person
{
Person(CheckDelegate InCheckDelegate)
{
this.Check = InCheckDelegate;
}
public CheckDelegate Check;
};
발행 구독 디자인 패턴
- 푸시(Push)형태의 알림(Notification)을 구현하는데 적합한 디자인 패턴
발행자와 구독자로 구분
-> 콘텐츠 제작자는 콘텐츠를 생상
-> 발행자는 콘텐츠를 배포
-> 구독자는 배포된 콘텐츠를 받아 소비
-> 제작자와 구독자가 서로를 몰라도, 발행자를 통해 콘텐츠를 생상하고 전달 가능 (느슨한 결합)
발행 구독 디자인 패턴의 장점
-> 제작자와 구독자는 서로를 모르기 때문에 느슨한 결합
-> 유지 보수가 쉽고, 유연하게 활용, 테스트가 쉬워짐
-> 시스템 스케일을 유연하게 조절할 수 있으며, 기능 확장이 용이하다
델리게이트 선언시 고려사항
고려사항
- 몇개의 인자를 전달
- 어떤 방식으로 전달
- 일대일로 전달
- 일대다로 전달
프로그래밍 환경 설정
- C++ 프로그래밍에서만 사용할 것인가?
- UFUNCTION으로 지정된 블루프린트 함수와 사용할 것인가?
어떤 함수와 연결할 것인가?
- 클래스 외부에 설계된 C++함수와 연결
- 전역에 설계된 정적 함수와 연결
- 언리얼 오브젝트의 멤버 함수와 연결 (대부분의 경우에 이 방식을 사용)
델리게이트 선언 매크로
DECLARE_{델리게이트유형}DELEGATE{함수정보}
델리게이트 유형
- 일대일 형태로 C++만 지원한다면 유형은 공란으로 둔다
-> DECLARE_DELEGATE- 일대다 형태로 C++만 지원한다면 MULTICAST를 선언한다
-> DECLARE_MULTICAST- 일대일 형태로 블루프린트를 지원한다면 DYNAMIC을 선언한다
-> DECLARE_DYNAMIC- 일대 다 형태로 블루프린트를 지원한다면 DYNAMIC과 MULTICAST를 조합한다
-> DECLARE_DYNAMIC_MULTICAST
함수 정보: 연동될 함수 형태를 지정한다
- 인자가 없고 반환값도 없으면 공란으로 둔다
-> DECLARE_DELEGATE- 인자가 하나고 반환값이 없으면 OneParam으로 지정한다
-> DECLARE_DELEGATE_OneParam- 인자가 세 개고 반환값이 있으면 RetVal_ThreeParams로 지정한다
-> DECLARE_DELEGATE_RetVal_ThreeParams (MULTICAST는 반환값을 지원하지 않음)- 최대 9개까지 지원
학교에서 진행하는 온라인 수업 활동 예시
학사 정보(CourseInfo)와 학생(Student)
-> 학교는 학사 정보를 관리
-> 학사 정보가 변경되면 자동으로 학생에게 알려준다.
-> 학생은 학사 정보의 알림 구독을 해지할 수 있다.
시나리오
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "UObject/NoExportTypes.h"
#include "CourseInfo.generated.h"
//1.
DECLARE_MULTICAST_DELEGATE_TwoParams(FCourseInfoOnChangedSignature, const FString&, const FString&);
/**
*
*/
UCLASS()
class UNREALDELEGATE_API UCourseInfo : public UObject
{
GENERATED_BODY()
public:
UCourseInfo();
//2.
FCourseInfoOnChangedSignature OnChanged;
//3.
void ChangedCourseInfo(const FString& InSchoolName, const FString& InNewContents);
private:
//4.
FString Contents;
};
// Fill out your copyright notice in the Description page of Project Settings.
#include "CourseInfo.h"
UCourseInfo::UCourseInfo()
{
Contents = TEXT("기존 학사 정보");
}
void UCourseInfo::ChangedCourseInfo(const FString& InSchoolName, const FString& InNewContents)
{
Contents = InNewContents;
UE_LOG(LogTemp, Log, TEXT("[CourseInfo] 학사 정보가 변경되어 알림을 발송합니다."));
OnChanged.Broadcast(InSchoolName,Contents);
}
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "Person.h"
#include "LessonInterface.h"
#include "Student.generated.h"
/**
*
*/
UCLASS()
class UNREALDELEGATE_API UStudent : public UPerson,public ILessonInterface
{
GENERATED_BODY()
public:
UStudent();
virtual void DoLesson() override;
void GetNotification(const FString& School, const FString& NewCourseInfo);
};
// Fill out your copyright notice in the Description page of Project Settings.
#include "Student.h"
#include "Card.h"
UStudent::UStudent()
{
Name = TEXT("이학생");
Card->SetCardType(ECardType::Student);
}
void UStudent::DoLesson()
{
ILessonInterface::DoLesson();
UE_LOG(LogTemp, Log, TEXT("%s님은 공부합니다."), *Name);
}
void UStudent::GetNotification(const FString& School, const FString& NewCourseInfo)
{
UE_LOG(LogTemp, Log, TEXT("[Student] %s님이 %s로부터 받은 메시지 : %s"), *Name, *School, *NewCourseInfo);
}
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "Engine/GameInstance.h"
#include "MyGameInstance.generated.h"
/**
*
*/
UCLASS()
class UNREALDELEGATE_API UMyGameInstance : public UGameInstance
{
GENERATED_BODY()
public:
UMyGameInstance();
virtual void Init() override;
private:
// 학교를 우리가 지금 대상으로 선언했고
//학교는 학사 시스템을 소유해줘야 하므로, MyGameInstance에서도 학사 시스템을 소유하도록 CourseInfo 를 선언하는데
// CourseInfo는 언리얼 오브젝트이고, 포인터로 관리를 해야하는데 포인터로 관리하기에 전방 선언이 가능
// 전방 선언으로 멤버 변수로 지정할때는 TObjectPtr을 사용
UPROPERTY()
TObjectPtr<class UCourseInfo> CourseInfo;
UPROPERTY()
FString SchoolName;
};
// Fill out your copyright notice in the Description page of Project Settings.
#include "MyGameInstance.h"
#include "Student.h"
#include "Teacher.h"
#include "Staff.h"
#include "Card.h"
#include "CourseInfo.h"
UMyGameInstance::UMyGameInstance()
{
SchoolName = TEXT("학교");
}
void UMyGameInstance::Init()
{
Super::Init();
//NewObject<UCourseInf>(아우터 지정); -> 이전 시간에 했던 컴포지션 관계에서 아우터를 지정 가능
// 우리가 생성한 객체는 클래스 멤버 변수에 들어가서 앞으로 관리를 받고있고, 특별한 일이 있지 않는 한 메모리에 계속 유지가 될텐데
// 이떄 MyGameInstance는 CourseInfo를 포함해줘야 한다.
//this를 넣어주면 CourseInfo는 MyGameInstance의 서브 오브젝트가 되고
// MyGameInstance는 CourseInfo의 Outer가 된다. -> 컴포지션 관계 성립
CourseInfo = NewObject<UCourseInfo>(this);
UE_LOG(LogTemp, Log, TEXT("========================"));
//밑에 Student를 생성하는 구문은 실행되면 오브젝트가 자동으로 소멸되기에 굳이 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"));
//AddUObject 함수를 사용하면, 첫번쨰 인자로 어떠한 클래스 인스턴스를 지정하고, 멤버 함수를 직접 묶을 수 있다.
CourseInfo->OnChanged.AddUObject(Student1, &UStudent::GetNotification);
CourseInfo->OnChanged.AddUObject(Student2, &UStudent::GetNotification);
CourseInfo->OnChanged.AddUObject(Student3, &UStudent::GetNotification);
// 마지막으로 학사 정보 변경
CourseInfo->ChangedCourseInfo(SchoolName, TEXT("변경된 학사 정보"));
UE_LOG(LogTemp, Log, TEXT("========================"));
}
LogTemp: ========================
LogTemp: [CourseInfo] 학사 정보가 변경되어 알림을 발송합니다.
LogTemp: [Student] 학생3님이 학교로부터 받은 메시지 : 변경된 학사 정보
LogTemp: [Student] 학생2님이 학교로부터 받은 메시지 : 변경된 학사 정보
LogTemp: [Student] 학생1님이 학교로부터 받은 메시지 : 변경된 학사 정보
LogTemp: ========================
이로써, 학사 정보를 학생에게 전달하는 기능을 만들었는데
학사정보와 하생은 어떠한 의존 관계도 가지지 않는다.
이게 느슨한 결합구조가 가지는 장점이다.