메모내용

Composition

Composition

객체 지향프로그래밍에서의 컴포지션 이란? 객체 지향 프로그래밍에서의 설계는 크게 상속과 컴포지션 의 활용으로 요약할 수 있는데, 상속은 성질이 같은 부모클래스와 자식 클래스와의 관계를 의미하는 Is-A 관계라고 보통 이야기 하고. 컴포지션은 성질이 다른 두 객체에서 어떤 객체가 다른 객체를 소유하는 Has-A 관계라고 요약할 수 있습니다. 컴포지션을 활용하면, 복합적인 기능을 거대한 클래스를, 효과적으로 설계하는데 유용하게 사용할 수 있습니다.

모던 객체 설계 기법과 컴포지션

모던 객체 설계 기법 SOLID 를 명심하여 설계해야 합니다.

모던 객체 설계 기법의 설계 핵심은 상속을 단순화하고, 단순한 기능을 가진 다수의 객체를 조합해 복잡한 객체를 구성하는데 있습니다.

언리얼 엔진에서의 컴포지션 구현 방법

컴포지션 설계 예시

카드를 가진 사람

Card.h
                
                    #pragma once
                    #include "CoreMinimal.h"
                    #include "UObject/NoExportType.h"
                    #include "Card.generated.h"

                    /** 카드의 유형을 나타내는 카드타입 열거형*/
                    UENUM()
                    enum class ECardType : uint8
                    {
                        STUDENT = 1 UMETA(DisplayName = "ForStudent"),      // 학생
                        TEACHER     UMETA(DisplayName = "ForTeacher"),      // 선생님
                        OUTSIDER    UMETA(DisplayName = "OutSider"),        // 외부인
                        INVALID
                    }

                    /** 사람이 지니고 있어야할 카드 를 표현하는 클래스 */
                    UCLASS()
                    class MODULENAME_API UCard : UObject
                    {
                        GNERATED_BODY()

                    public:

                        UCard();

                        UPROPERTY()
                        int32 IdNumber;

                        UPROPERTY()
                        ECardType CardType;

                    public:

                        FORCEINLINE GetCardIdNumber(){return IdNumber;}
                        FORCEINLINE void SetCardIdNumber(int32 NewIdNumber){IdNumber = NewIdNumber;}
                        
                        FORCEINLINE ECardType GetCardType(){return Type;}
                        FORCEINLINE void SetCardType(ECardType NewCardType){CardType = NewCardType;}
                    }
                
            
Card.cpp
                
                    #inlude "Card.h"

                    UCard::UCard()
                    {
                        IdNumber = 0;
                        CardType = ECardType::INVALID;
                    }
                
            
Person.h
                
                    #pragma once
                    #include "CoreMinimal.h"
                    #include "UObject/NoExportType.h"
                    #include "Person.generated.h"

                    /** 하나의 사람을 표현하는 클래스 */
                    UCLASS()
                    class MODULENAME_API UPerson : public UObject
                    {
                        GNERATED_BODY()

                    public:

                        UPerson();

                        UPROPERTY()
                        FString Name;

                        UPROPERTY()
                        TObjectPtr<class UCard> Card; // 다른 클래스를 소유합니다.(컴포지션) , 최대한 의존관계를 없애기 위해 '전방선언' 합니다.
                        /**
                            오브젝트 포인터 를 선언하는 방식은
                            UE4 까지는 * 로 사용하였지만,
                            UE5 부터는 다른 방법으로 하기를 권장하고 있다.
                            선언부에서는, 원시포인터 * 대신, TObjectPtr<> 을 사용하도록 권장하고,
                            구현부에서는 그냥 * 를 사용해도 된다.
                            
                            즉,
                            class UCard* Card; 로 사용했던것을
                            UObjectPtr<class UCard> 로 사용하도록 한다.

                            또한
                            함수의 인수, 반환형에는 * 를 쓰고
                            TArray, TMap 에서도 템플릿으로는 * 을 쓴다.

                            UObject* GetObject();

                            TArray<UObject*>
                        */
                    }
                
            
Person.cpp
                
                    #include "Person.h"
                    #include "Card.h"

                    UPerson::UPerson()
                    {
                        Name = TEXT("홍길동");

                        // 생성자 단계, 즉 CDO 에서 클래스를 생성하려면 CreatureDefaultSubobject API 를 사용해야 합니다.
                        Card = CreatureDefaultSubobject<UCard>(TEXT("Name_Card"));
                    }