강의 목표 :
- 모던 객체지향 설계에서 느슨한 결합의 장점 이해하기
- 느슨한 결합을 편리하게 구현하도록 도와주는 델리게이트 이해하기
- 발행/구독 디자인 패턴 이해하고 구현해보기
강의 내용 :
- 언리얼 델리게이트를 사용해 클래스 간의 느슨한 결합을 구현
- 느슨한 결합이 가지는 장점 : 시스템 변경 사항에 대해 손쉽게 대처할 수 있음
- 느슨한 결합으로 구현된 발행/구독 모델의 장점
- 언리얼 C++ 델리게이트의 선언 방법과 활용
[ ✔️강한 결합과 느슨한 결합 ]
- 강한 결합 (Tight Coupling) : 클래스들이 서로 의존성을 가지는 경우를 의미함
- 느슨한 결합 (Loose Coupling) : 실물에 의존하지 말고 추상적 설계에 의존하라 (DIP 원칙)
ex)
Person은 카드를 소유하고 카드를 통해서 출입한다.
왼쪽의 경우 Person이 Card를 소유하고 있기 때문에 Card가 없는 경우 Person이 만들어질 수 없다.
이런 경우 Person은 Card에 대한 의존성을 가진다고 한다.
DIP 원칙을 활용하여 출입에 관한 설계를 하고 카드가 이를 구현하게 만든다.
그러면 클래스간의 의존성이 떨어지게 된다.
Person이 Card가 필요한 이유가 출입확인을 하기 위함이라고 한다면,
출입에 관련된 추상적인 클래스(ICheck) 를 설계하고 Person이 ICheck 를 가지도록 한다.
그렇게되면 Person은 출입에 관련된 추상적인 설계에 의존하게된다.
어떠한 카드든 ICheck 를 상속받아 행동(출입)에 관련된 추상 가상함수를 구현한다면 Person은 해당 카드를 통해 출입을 할 수 있게된다.
또한 카드가 아니라 출입을 인증할 수 있는 다른 수단이 생긴경우 ICheck 를 상속해서 추상 가상함수를 구현 해주면된다.
이러한 느슨한 결합 구조는 유지 보수를 손쉽게 만들어준다.
📍느슨한 결합의 간편한 구현 (델리게이트 , Delegate) :
델리게이트의 사전적 의미는 "대리자"이다.
예시 이미지 :

매번 행동에 중심을 둔 인터페이스를 만드는 작업이 번거로울 수 있다.
행동(함수)을 오브젝트처럼 관리하면 어떨까?
C++은 델리게이트를 지원하지 않고, C#은 지원한다.
언리얼 C++이 지원하는 델리게이트를 활용하면 느슨한 결합 구조를 간편하고 안정적으로 구현할 수 있다.
📍델리게이트의 장점 :
- 클래스는 자신이 해야 할 작업에만 집중할 수 있음
- 외부에서 발생한 변경 사항에 대해 영향을 받지 않음
- 자신이 기능을 확장하더라도 다른 모듈에 영향을 주지 않음
[ ✔️언리얼 델리게이트 ]
델리게이트
C++ 오브젝트 상의 멤버 함수를 가리키고 실행시키는 데이터 유형입니다.
dev.epicgames.com
델리게이트를 통해 C++ 오브젝트 상의 멤버 함수 호출을 일반적이고 유형적으로 안전한 방식으로 할 수 있다.
델리게이트에 바인딩할 때, 페이로드 데이터를 같이 전해줄 수 있다.
페이로드 데이터란 바인딩된 함수를 불러낼 때(invoke) 전해지는 임의의 변수를 의미한다.
📍언리얼 델리게이트 선언 매크로 :
델리게이트 유형 (어떤유형의 델리게이트인가?)
- DECLARE_[공란] : 일대일 형태로 C++만 지원한다면 유형은 "공란"으로 둔다
- DECLARE_MULTICAST : 일대다 형태로 C++만 지원한다면 MULTICAST를 선언한다.
- DECLARE_DYNAMIC : 일대일 형태로 블루프린트를 지원한다면 DYNAMIC을 선언한다.
- DECLARE_DYNAMIC_MULTICAST : 일대다 형태로 블루프린트를 지원한다면 DYNAMIC 과 MULTICAST 조합
📍언리얼 델리게이트 함수 정보 (연동 될 함수 형태 지정) :
- 공란 : 인자가 없고 반환값도 없으면
ex) DECLARE_DELEGATE - OneParam : 인자가 하나고 반환값이 없으면
ex) DECLARE_DELEGATE_OneParam - TwoParams : 인자가 두 개
ex) DECLARE_DELEGATE_TwoParam - RetVal_ThreeParams : 인자가 세 개고 반환값이 있으면 (파라미터는 최대 9개 까지 지원)
ex) DECLARE_DELEGATE_RetVal_ThreeParams - MULTICAST 는 RetVal(= Return Value) 지원하지 않음, DYNAMIC은 RetVal 지원함
📍언리얼 델리게이트 매크로 선정 예시 :
학사 정보가 변경되면 알림 주체와 내용을 학생에게 전달된다. (두개의 인자)
변경된 학사 정보는 다수 인원을 대상으로 발송한다. (MULTICAST)
오직 C++ 프로그래밍에서만 사용한다. (DYNAMIC 사용 x)
DECLARE_MULTICAST_DELEGATE_TwoParams
📍델리게이트 사용 예제 :
Log 를 출력해주는 함수 WriteToLog(FString) 이 있다.
이 함수를 객체처럼 만들고자 한다.
class FLogWriter
{
void WriteToLog( FString );
};
델리게이트 매크로를 활용하여 함수의 형태에 대해 지정한다.
- 델리게이트 유형 : FStringDelegate
- 반환값 : 없음
- 인자 : 1개, FString
DECLARE_DELEGATE_OneParam( FStringDelegate, FString );
// DECLARE_[공란]_DELEGATE_[인자1개]([델리게이트 유형],[인자타입]);
임의 클래스에서 델리게이트 타입 변수 선언
class FMyClass
{
FStringDelegate WriteToLogDelegate;
}
델리게이트 할당(바인딩)
TSharedRef< FLogWriter > LogWriter( new FLogWriter() );
// 바인딩
WriteToLogDelegate.BindSP( LogWriter, &FLogWriter::WriteToLog );
델리게이트 사용법
델리게이트에 함수를 바인딩 하기 전 Execute() 를 호출하면 assert 가 발동한다.
이런 경우를 피라기 위해 대부분 ExecuteIfBound() 함수를 사용한다.
// case1
WriteToLogDelegate.Execute( TEXT( "델리게이트 쥑이네!" ) );
// case2
WriteToLogDelegate.ExecuteIfBound( TEXT( "함수가 바인딩되었을 때만 실행!" ) );
[ ✔️발행 구독 디자인 패턴 ]
푸시(Push) 형태의 알림을 구현하는데 적합한 디자인 패턴
"발행자/콘텐츠 제작자" 와 "구독자"로 나뉜다.
콘텐츠 제작자는 콘텐츠를 생산하고 발행자는 이를 배포한다.
구독자는 배포된 콘텐츠를 받아 소비한다.
제작자와 구독자는 서로를 몰라도, 발행자를 통해 콘텐츠를 생산하고 전달할 수 있게된다. (느슨한 결합)
📍발행 구독 디자인 패턴의 장점 :
- 제작자와 구독자는 서로를 모르기 때문에 느슨한 결합으로 구성된다.
- 유지보수(Maintenance)가 쉽고, 유연하게 활용될 수 있으며(Flexibility), 테스트가 쉬워진다.
- 시스템 스케일을 유연하게 조절할 수 있으며(Scalability), 기능 확장(Extensibility)이 용이하다.
📍실습 :
학교에서 진행하는 온라인 수업 활동 예시 ( 학사정보(CourseInfo) 와 학생(Student) )
- 학교는 학사 정보를 관리한다.
- 학사 정보가 변경도면 자동으로 학생에게 알려준다.
- 학생은 학사 정보의 알림 구독을 해지할 수 있다.
학사 정보와 3명의 학생이 있다. 시스템에서 학사 정보를 변경한다.
학사 정보가 변경되면 알림 구독한 학생들에게 변경 내용을 자동으로 전달한다.
설계 :
- 학사 정보는 구독과 알림을 대행할 델리게이트(대리자)를 선언
- 학생은 학사 정보의 델리게이트를 통해 알림을 구독
- 학사 정보는 내용 변경시 델리게이트를 사용해 등록한 학생들에게 알림
고려사항 :
어떤 데이터를 전달하고 받을 것인지? 인자의 수와 각각의 타입을 설계
- 몇 개의 인자 전달?
- 어떤 방식으로 전달?
- 일대일로 전달 / 일대다로 전달
프로그래밍 환경 설정
- C++ 프로그래밍에서만 사용할 것인가?
- UFUNCTION으로 지정된 블루프린트 함수와 사용할 것인가?
어떤 함수와 연결할 것인가?
- 클래스 외부에 설계된 C++ 함수와 연결
- 전역에 설계된 정적 함수와 연결
- 언리얼 오브젝트의 멤버 함수와 연결 (대부분의 경우 이 방식 채택)
실습을 위한 델리게이트 설계 :
학사 정보 클래스와 학생 클래스의 상호 의존성을 최대한 없앤다. (서로 include 하지 말것)
- 하나의 클래스는 하나의 작업에만 집중하도록 설계
- 학사 정보 클래스는 델리게이트를 선언하고 알림에만 집중
- 학생 클래스는 알림을 수신하는데만 집중
- 직원도 알림을 받을 수 있도록 유연하게 설계
- 학사 정보와 학생은 서로 헤더를 참조하지 않도록 신경쓸 것
이를 위해 발행과 구독을 컨트롤하는 주체(학교, MyGameInstance)를 선정
코드 :
CourseInfo Class :
// CourseInfo.h (UObject)
// 델리게이트 매크로를 통해 함수의 형태 지정
// 델리게이트 유형 뒤에 "Signature" 를 붙여주는게 관행이라고 한다.
DECLARE_MULTICAST_DELEGATE_TwoParams(FCourseInfoOnChangedSignature
, const FString&
, const FString&);
UCLASS()
class UNREALDELEGATE_API UCourseInfo : public UObject
{
GENERATED_BODY()
public:
UCourseInfo();
// 클래스 내부에 델리게이트 선언
FCourseInfoOnChangedSignature OnChanged;
void ChangeCourseInfo(const FString& InSchoolName, const FString& InNewContents);
private:
FString Contents;
};
// CourseInfo.cpp
UCourseInfo::UCourseInfo()
{
Contents = TEXT("기존 학사 정보");
}
void UCourseInfo::ChangeCourseInfo(const FString& InSchoolName, const FString& InNewContents)
{
Contents = InNewContents;
UE_LOG(LogTemp, Log, TEXT("[CouresInfo] 학사 정보가 변경되어 알림을 발송합니다."));
OnChanged.Broadcast(InSchoolName, Contents);
}
MyGameInstance Class :
// MyGameInstance.h
private:
// 멤버 변수(프로퍼티) 추가
UPROPERTY()
TObjectPtr<class UCourseInfo> CourseInfo;
// MyGameInstance.cpp
#pragma region Composition
// 인자로 Outer 설정 가능
CourseInfo = NewObject<UCourseInfo>(this);
UE_LOG(LogTemp, Log, TEXT("======================"));
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->ChangeCourseInfo(SchoolName, TEXT("변경된 학사 정보"));
UE_LOG(LogTemp, Log, TEXT("======================"));
#pragma endregion
결과 :

📍코드 분석 :
델리게이트 매크로를 활용하여 "델리게이트 유형" 과 "함수의 형태" 를 지정
델리게이트 선언
UCourseInfo 타입 객체 CourseInfo 로 부터 델리게이트에 접근하여 함수를 바인딩해준다.
델리게이트 매크로 선언시 함수의 인자가 FString 타입 인자 2개였기에 UStudent::GetNotification() 함수는 해당 형태를 맞춰준다.
UCourseInfo::ChangeCourseInfo() 함수 내부에서 델리게이트가 호출되고 있다.
Broadcast() 함수를 통해 델리게이트에 바인딩 된 함수들을 한꺼번에 호출한다.
출력창을 보면 OnChanged.BroadCast() 함수 호출 한번으로 "[Student] ~" 메세지가 3개 출력된 것을 확인할 수 있다.
================================
개인 공부 기록용 포스팅입니다.
댓글, 질문 환영해요~!
Unreal Engine Ver : 5.1.1
Refer : Inflearn_이득우의 언리얼 프로그램
================================
'Unreal > Inflearn' 카테고리의 다른 글
Part1.10 언리얼 컨테이너 라이브러리 Ⅰ - Set (0) | 2024.04.11 |
---|---|
Part1.10 언리얼 컨테이너 라이브러리 Ⅰ - Array (0) | 2024.04.11 |
Part1.8 언리얼 C++ 설계 Ⅱ - 컴포지션⭕ (0) | 2024.04.09 |
Part1.7 언리얼 C++ 설계 Ⅰ - 인터페이스⭕ (0) | 2024.04.09 |
Part1.6 언리얼 오브젝트 리플렉션 시스템 Ⅱ⭕ (0) | 2024.04.08 |