서론
- 최근에 언리얼 엔진 액터 컴포넌트 쪽 분석을 했는데 팀원 중 한 분이 액터 컴포넌트 메서드 중 PhysicCreate 하는 부분에 대해서 오버라이딩을 할 함수를 호출하는 것과 멀티캐스트 델리게이트를 호출하는 것에 대한 차이점에 의문을 품었다.
구조
- PhysicsCreate라는 메서드가 있을 때 그 메서드 내에서 OnPhysicsCreate 라는 메서드를 호출하고 그 다음 줄에서 PhysicsCreate.Broadcast 델리게이트를 전파하는 구조
- 비슷한 처리를 하는건데 왜 오버라이딩 버전과 델리게이트 버전으로 2개를 구분해놨는가 ?
예제
- 친근한 예로 유튜브에 어떤 동영상이 올라오면 구독한 사람에 한해서 알람이 가는 경우를 생각해봤음
- Youtuber의 Upload 메서드 내에서 OnUpload ( 오버라이딩 버전 ) 과 UploadDelegate 들을 호출하는 ( C++에서 흉내낸 멀티캐스트 델리게이트 ) 것의 차이를 구분해 봄
- 결론적으로 OnUpload를 오버라이딩한 함수의 경우, 동영상의 품질 설정이나 동영상의 유료 컨테츠를 쓰겠는가 하는 부분에 대한 처리를 맡았고, UploadDelegate는 구독자들이 구독을 하게 된 경우 동영상이 올라가는 시점에 구독자들에게 알람이 갈 수 있게 하고 구독자마다 그 시점의 동작을 정의할 수 있게 했음
- 오버라이드 버전은 동영상에 대한 처리, 멀티캐스트 델리게이트는 구독자에 대한 처리를 할 수 있었음
- 언리얼 엔진의 델리게이트의 경우 디자인 패턴 중 구독 - 발행 (Observer) 패턴을 모티브로 만들었으며 구독 - 발행 패턴이 가지는 가장 큰 이점은 불특정 다수에 대한 등록의 제한이 없다는 것이다. 아래 main 에서도 볼 수 있듯이 subscriber는 본인이 구독하고 싶은 youtuber에게 subscribe를 하게 되면 유튜버는 동영상이 올라가는 시점에 모든 subscriber에게 알람을 보내줄 수 있게 된다.
#include <iostream>
#include <vector>
#include <string>
#include <functional>
class Youtuber
{
public:
using OnUploadDelegate = std::function<void(Youtuber*)>;
Youtuber(std::string name) : name(name) {}
public:
void Upload()
{
//업로드에 대한 전반적인 처리, 동영상 품질 및 유료 시스템 결제 등
//또한 UploadDelegate(Broadcast)들에 대해 호출(Notify)하기 전에 본인의 값 세팅이 완료될 수 있게끔 순서를 보장하기도 함
OnUpload();
for (auto& Upload : UploadDelegate)
{
Upload(this);
}
}
protected:
virtual void OnUpload() {}
public:
void AddSubscriber(OnUploadDelegate fn)
{
UploadDelegate.push_back(fn);
}
public:
std::string GetName() { return name; }
private:
std::vector<OnUploadDelegate > UploadDelegate;
std::string name;
};
class Youtuber_Woojin : public Youtuber
{
public:
Youtuber_Woojin(std::string name) : Youtuber(name) {}
protected:
virtual void OnUpload() override
{
std::cout << "동영상 품질을 1080p로 올렸습니다!!" << std::endl;
std::cout << "동영상을 프리미엄으로 등록합니다!!" << std::endl;
}
};
class Subscriber
{
public:
Subscriber(std::string name) : name(name) {}
void SubScribe(Youtuber* youtuber)
{
youtuber->AddSubscriber(GetNotify());
}
protected:
virtual Youtuber::OnUploadDelegate GetNotify()
{
return [](Youtuber* youtuber) {
std::cout << youtuber->GetName() << "님꼐서 새 동영상을 업로드하였습니다!" << std::endl;
};
}
public:
std::string GetName() { return name; }
private:
std::string name;
};
class Subscriber1 : public Subscriber
{
public:
using Subscriber::Subscriber;
protected:
virtual Youtuber::OnUploadDelegate GetNotify() override
{
return [](Youtuber* youtuber) {
std::cout << youtuber->GetName() << "님께서 새 동영상을 업로드하였습니다" << std::endl;
std::cout << "동영상을 보지 않습니다!" << std::endl;
};
}
};
class Subscriber2 : public Subscriber
{
public:
using Subscriber::Subscriber;
protected:
virtual Youtuber::OnUploadDelegate GetNotify() override
{
return std::bind(&Subscriber2::Notify_Internal, this, std::placeholders::_1);
}
private:
void Notify_Internal(Youtuber* youtuber)
{
std::cout << youtuber->GetName() << "님께서 새 동영상을 업로드하였습니다" << std::endl;
std::cout << GetName() << "님은 메시지를 무시했습니다!" << std::endl;
}
};
int main()
{
Youtuber* big_youtuber = new Youtuber_Woojin("woojin");
Subscriber* subscriber1 = new Subscriber("subscriber1");
Subscriber* subscriber2 = new Subscriber1("subscriber2");
Subscriber* subscriber3 = new Subscriber2("subscriber3");
subscriber1->SubScribe(big_youtuber);
subscriber2->SubScribe(big_youtuber);
subscriber3->SubScribe(big_youtuber);
big_youtuber->Upload();
delete subscriber3;
delete subscriber2;
delete subscriber1;
delete big_youtuber;
return 0;
}
깃허브