디자인 패턴이란?
프로그래밍 디자인 패턴은 소프트웨어 설계에서 자주 발생하는 문제에 대한 해결책을 제시하는 효율적인 방법론입니다.
이러한 패턴들은 공통된 설계 상황에 대해 검증된 솔루션을 제공하며, 코드의 유지보수성과 확장성을 높이고 객체 간의 상호작용을 잘 조직화할 수 있도록 도와줍니다. 디자인 패턴을 이해하고 적용함으로써 개발자는 반복적인 문제를 해결하는 데 탁월한 도구를 갖추게 됩니다.
게임 프로그래밍 에서는?
게임 프로그래밍에서는 Component, Observer, Factory, State, Command 등등 다양한 디자인 패턴이 존재합니다.
이러한 패턴들은 게임 오브젝트의 처리, 게임 이벤트의 처리, 캐릭터 상태관리, 인스턴스 유지 등 많은 설계 부분에서 검증된 솔루션을 제공합니다.
Intro
프로그래밍 디자인 패턴 에서 사용되는 요소 중 언리얼에서 사용되는 Interface에 대한 소개글을 작성하려 합니다.
본 글은 이득우의 언리얼 프로그래밍 Part1 - 언리얼 C++의 이해를 듣고 작성한 글입니다.
Interface의 역할
인터페이스는 객체가 반드시 구현해야 할 행동을 지정하는데 활용되는 타입입니다.
다형성을 구현하며 의존성이 분리된 설계에 유용하게 활용됩니다.
말로만으로는 이해가 잘 가지 않으므로 C++을 이용한 구체적인 예시를 하나 들어보려고 합니다.
인터페이스를 정의하는 클래스
class IShape {
public:
virtual ~IShape() {} // 가상 소멸자
virtual double area() const = 0; // 면적을 계산하는 순수 가상 함수
virtual void draw() const = 0; // 도형을 그리는 순수 가상 함수
};
C++은 다른 객체지향 언어와 달리 Interface라는 개념이 명시적으로 제공되지 않습니다.
그래서 C++ Class를 Interface 개념에 맞게끔 사용합니다.
다음은 가상함수만을 선언한 클래스를 생성해서 상속받은 클래스가 반드시 구현해야 하는 행동을 지정합니다.
인터페이스를 구현하는 클래스
// IShape 인터페이스를 구현하는 원 클래스
class Circle : public IShape {
private:
double radius;
public:
Circle(double r) : radius(r) {}
virtual double area() const override {
return 3.14159 * radius * radius;
}
virtual void draw() const override {
std::cout << "원을 그립니다." << std::endl;
}
};
// IShape 인터페이스를 구현하는 사각형 클래스
class Rectangle : public IShape {
private:
double width;
double height;
public:
Rectangle(double w, double h) : width(w), height(h) {}
virtual double area() const override {
return width * height;
}
virtual void draw() const override {
std::cout << "사각형을 그립니다." << std::endl;
}
};
다음의 예시는 IShape 인터페이스를 상속받아 이를 구현하는 두 개의 클래스를 정의한 예시입니다.
가상함수 area(), draw()를 각각 구현함으로써 같은 이름의 메서드가 다양한 동작을 할 수 있습니다.
이러한 개념은 객체지향 프로그래밍에서 다형성의 원칙을 충족합니다.
그리고 두 클래스는 함수의 의존성을 IShape 인터페이스에만 의존하도록 분리하여 확장 가능한 설계를 만들었습니다.
이는 의존성 분리에 해당합니다.
UE에서의 인터페이스 선언
#pragma once
#include "ReactToTriggerInterface.generated.h"
UINTERFACE(Blueprintable)
class UReactToTriggerInterface : public UInterface
{
GENERATED_BODY()
};
class IReactToTriggerInterface
{
GENERATED_BODY()
public:
/** 이 오브젝트를 활성화시키는 트리거 볼륨에 반응합니다. 반응에 성공하면 true 를 반환합니다. */
UFUNCTION(BlueprintCallable, BlueprintImplementableEvent, Category="Trigger Reaction")
bool ReactToTrigger() const;
};
UE에서 인터페이스를 적용하기 위해서는 UINTERFACE 매크로를 사용하고, UInterface를 상속해야 합니다.
UReactToTriggerInterface는 실제 인터페이스가 아닌 언리얼 엔진의 리플렉션 시스템에 참조되도록 하기 위해서만 존재하는 비어있는 클래스입니다.
IReactToTriggerInterface 인터페이스에서 실제 설계를 선언하게 됩니다.
다른 클래스에서 인터페이스 구현을 위해 상속하면 실제 인터페이스는 같은 클래스 이름에서 첫 글자만 U에서 I로 바뀝니다.
UE에서의 인터페이스 구현
class ATrap : public AActor, public IReactToTriggerInterface
{
GENERATED_BODY()
public:
virtual bool ReactToTrigger() const override;
};
새 클래스에서 인터페이스를 사용하기 위해서는 접두사가 I인 인터페이스 클래스를 상속하여 구현하면 됩니다.
요약
- 인터페이스는 디자인 패턴에서 활용하는 요소 중 하나입니다.
- 객체가 반드시 구현해야할 행동을 지정하는 개념입니다.
- 설계에서 다형성과 의존성 분리에 대한 역할을 담당합니다.
- C++ 에서는 가상 함수를 이용해서 인터페이스를 구현합니다.
- 순수 가상 함수는 상속받는 클래스가 반드시 구현해야 합니다.
이를 통해 객체가 반드시 구현해야할 행동을 지정할 수 있습니다. - 상속받은 클래스가 같은 이름의 가상함수를 각각 다르게 구현함으로써 다형성을 충족하고
각 클래스는 상속받은 인터페이스에 대해서만 의존성을 가지므로 의존성 분리가 성립합니다.
- 순수 가상 함수는 상속받는 클래스가 반드시 구현해야 합니다.
- 언리얼에서 인터페이스를 생성하면 두 개의 클래스가 생성됩니다.
- U로 시작하는 타입 클래스
- I로 시작하는 인터페이스 클래스
- 객체를 설계할 때는 I 인터페이스 클래스를 사용합니다.
- U타입 클래스 정보는 런타임에서 인터페이스 구현 여부를 파악하는데 사용됩니다.
- 실제로 U 타입 클래스를 작업할 일은 없습니다.
- 인터페이스에서 관련된 구성과 구현은 I 인터페이스 클래스에서 진행됩니다.
- C++ 인터페이스의 특징
- 추상 타입으로만 선언할 수 있는 Java, C#과 달리 C++은 인터페이스에서도 구현이 가능합니다.
'Unreal' 카테고리의 다른 글
UE 액터 컴포넌트 (1) | 2024.06.08 |
---|---|
UE C++ 빌드 시스템 (0) | 2024.06.01 |
UE C++ 직렬화 (0) | 2024.05.25 |
UE C++ 메모리 관리 (0) | 2024.05.19 |
UE C++ 오브젝트 (1) | 2024.04.13 |