8.1 컴포지션
8.1-1 컴포지션 개요
- 개체 지향 설계에서 상속이 가진 Is-A 관계만 의존해서는 설계와 유지보수가 어려움.
상속의 깊이가 길고, 최상위 부모 클래스의 코드가 변경되면 그 하위 모든 자식 클래스가 영향을 받기 때문.
- 컴포지션은 개체 지향 설계에서 Has-A 관계를 구현하는 설계 방법.
복잡한 기능을 가져야 하는 클래스를 효과적으로 설계하는데 유용함.
- 모던 개체 설계 기법의 핵심은 상속을 단순화하고,
단순한 기능을 가진 개체를 조합해서 복잡한 개체를 구성하는 것.
그래서 컴포지션은 아주 중요함.
8.1-2 언리얼 엔진에서의 컴포지션 구현 방법
- 하나의 언리얼 오브젝트에는 항상 클래스 기본 오브젝트가 있음.
- 언리얼 오브젝트 간의 컴포지션은 어떻게 구현할 것인가?
CDO에 미리 다른 언리얼 오브젝트를 생성 후 조합.(필수적 포함)
CDO에는 빈 포인터만 넣고, 런타임에서 언리얼 오브젝트를 생성 후 조합.(선택적 포함)
- 언리얼 오브젝트를 생성할 때 컴포지션 정보를 구축할 수 있음.
내가 소유한 언리얼 오브젝트를 Subobject라고 함.
나를 소유한 언리얼 오브젝트를 Outer라고 함.
8.1-3 컴포지션 실습
- 학교 구성원을 위해 출입증을 만들고자 함.
출입증을 Person에서 구현 후 상속? 아니면 컴포지션으로 분리?
- 상속 시키는 경우의 문제점
새로운 구성원이 필요해짐. 근데 그 구성원은 출입증이 필요 없는 구성원.
그럼 Person을 수정해야함. 하위 모든 자식 클래스에 영향을 줌.
- 새 프로젝트 "UnrealComposition" 생성
- 새 C++ 클래스 > UObject 부모 클래스 > "Card" 클래스 생성
<hide/>
// Card.h
#pragma once
#include "CoreMinimal.h"
#include "UObject/NoExportTypes.h"
#include "Card.generated.h"
UENUM()
enum class ECardType : uint8
{
Student = 1 UMETA(DisplayName = "For Student"),
Teacher UMETA(DisplayName = "For Teacher"),
Staff UMETA(DisplayName = "For Staff"),
Invalid
};
UCLASS()
class UNREALCOMPOSITION_API UCard : public UObject
{
GENERATED_BODY()
public:
UCard();
ECardType GetCardType() const { return CardType; }
void SetCardType(ECardType InCardType) { CardType = InCardType; }
private:
UPROPERTY()
ECardType CardType;
UPROPERTY()
uint32 Id;
};
<hide/>
// Card.cpp
#include "Card.h"
UCard::UCard()
{
CardType = ECardType::Invalid;
Id = 0;
}
<hide/>
// Person.h
#pragma once
#include "CoreMinimal.h"
#include "UObject/NoExportTypes.h"
#include "Person.generated.h"
UCLASS()
class UNREALCOMPOSITION_API UPerson : public UObject
{
GENERATED_BODY()
public:
...
FORCEINLINE class UCard* GetCard() const { return Card; }
FORCEINLINE void SetCard(class UCard* InCard) { Card = InCard; }
protected:
...
UPROPERTY()
TObjectPtr<class UCard> Card;
// UE5부터 로우 포인터를 TObjectPtr<>로 사용할 것이 권장됨.
// header에서는 tobjectptr, cpp에서는 로우포인터 써도됨.
};
<hide/>
// Person.cpp
#include "Person.h"
#include "Card.h"
UPerson::UPerson()
{
...
Card = CreateDefaultSubobject<UCard>(TEXT("NAME_Card"));
}
<hide/>
// Teacher.cpp
#include "Teacher.h"
#include "Card.h"
UTeacher::UTeacher()
{
Name = TEXT("이선생");
Card->SetCardType(ECardType::Teacher);
}
...
<hide/>
// Student.cpp
#include "Student.h"
#include "Card.h"
UStudent::UStudent()
{
Name = TEXT("이학생");
Card->SetCardType(ECardType::Student);
}
...
<hide/>
// Staff.cpp
#include "Staff.h"
#include "Card.h"
UStaff::UStaff()
{
Name = TEXT("이직원");
Card->SetCardType(ECardType::Staff);
}
<hide/>
// MyGameInstance.cpp
#include "MyGameInstance.h"
#include "Student.h"
#include "Teacher.h"
#include "Staff.h"
#include "Card.h"
UMyGameInstance::UMyGameInstance()
{
SchoolName = TEXT("기본학교");
}
void UMyGameInstance::Init()
{
Super::Init();
UE_LOG(LogTemp, Log, TEXT("============================"));
TArray<UPerson*> Persons = { NewObject<UStudent>(), NewObject<UTeacher>(), NewObject<UStaff>() };
for (const auto Person : Persons)
{
const UCard* OwnCard =Person->GetCard();
check(OwnCard);
ECardType CardType = OwnCard->GetCardType();
//UE_LOG(LogTemp, Log, TEXT("%s님이 소유한 카드 종류 %d"), *Person->GetName(), CardType);
const UEnum* CardEnumType = FindObject<UEnum>(nullptr, TEXT("/Script/UnrealComposition.ECardType"));
if (CardEnumType)
{
FString CardMetaData = CardEnumType->GetDisplayNameTextByValue((int64)CardType).ToString();
UE_LOG(LogTemp, Log, TEXT("%s님이 소유한 카드 종류 %s"), *Person->GetName(), *CardMetaData);
}
}
UE_LOG(LogTemp, Log, TEXT("============================"));
}
'Unreal > [서적] 언리얼5 이득우님 인프런1' 카테고리의 다른 글
Ch 10. Array and Set (0) | 2023.05.12 |
---|---|
Ch 09. 델리게이트 (0) | 2023.05.12 |
Ch 07. 언리얼 인터페이스 (0) | 2023.05.11 |
Ch 06. 언리얼 리플렉션 2 (0) | 2023.05.10 |
Ch 05. 언리얼 리플렉션 (0) | 2023.05.10 |
댓글