1. 캡슐화 (Encapsulation)
- 정의: 관련된 데이터(상태, 속성, 필드)와 해당 데이터를 조작하는 메서드(행위, 기능)를 하나의 단위(객체, 클래스)로 묶고, 객체의 내부 구현 세부 사항을 외부로부터 보호하고 숨기는(정보 은닉, Information Hiding) 것을 의미합니다.
- 설명: 캡슐화는 객체의 데이터를 외부에서 직접 접근하여 임의로 변경하는 것을 막고, 오직 객체가 외부에 공개하기로 결정한 메서드(또는 프로퍼티 접근자)를 통해서만 상태 변경이나 접근이 이루어지도록 제어합니다. 이는 접근 제한자(
public
,private
,protected
,internal
)와 프로퍼티(Property)를 통해 주로 구현됩니다.private
멤버는 클래스 내부에서만 접근 가능하게 하여 내부 구현을 숨기고,public
멤버는 외부와의 상호작용을 위한 인터페이스 역할을 합니다. - 이점:
- 데이터 보호 및 무결성 유지: 객체의 상태가 의도치 않게 변경되거나 잘못된 값으로 설정되는 것을 방지합니다.
- 유지보수성 향상: 객체 내부 구현을 변경하더라도, 공개된 인터페이스만 유지된다면 외부 코드에 미치는 영향을 최소화할 수 있습니다.
- 모듈성 증대: 객체가 독립적인 단위로 기능하므로 코드의 모듈성이 높아집니다.
- Unity 예시:
Player
클래스 내부에 플레이어 체력을 나타내는private int health;
필드를 두고, 외부에서는public void TakeDamage(int amount)
메서드를 통해서만 체력을 감소시키도록 합니다. 현재 체력 값을 외부에서 읽어야 한다면public int CurrentHealth { get { return health; } }
와 같은 읽기 전용 프로퍼티를 제공할 수 있습니다. 이렇게 하면 외부에서player.health = -100;
과 같이 직접적이고 잠재적으로 유효하지 않은 수정을 막을 수 있습니다. (이전 '필드와 프로퍼티' 포스팅 내용 참고)
2. 상속 (Inheritance)
- 정의: 한 클래스(자식 클래스 또는 파생 클래스)가 다른 클래스(부모 클래스 또는 기반 클래스)의 속성(필드)과 기능(메서드)을 물려받는 메커니즘입니다. 이를 통해 코드 재사용성을 높이고 클래스 간의 계층 구조("is-a" 관계, 예: '개는 동물이다')를 형성할 수 있습니다.
- 설명: 자식 클래스는 부모 클래스의 멤버(접근 제한자에 따라 접근 가능한)를 그대로 사용할 수 있으며, 필요에 따라 새로운 멤버를 추가하거나 부모 클래스의 메서드를 재정의(Override)하여 동작을 변경하거나 확장할 수 있습니다. C#은 클래스에 대해 단일 구현 상속만 지원합니다 (하나의 클래스는 오직 하나의 부모 클래스로부터만 직접 상속받을 수 있습니다).
- 이점:
- 코드 재사용: 공통된 속성과 기능을 부모 클래스에 정의하고 여러 자식 클래스가 이를 상려받아 사용함으로써 코드 중복을 줄입니다.
- 계층적 관계 표현: 현실 세계의 개념처럼 클래스 간의 계층 구조를 모델링할 수 있습니다.
- 다형성 구현의 기반: 상속은 다형성을 구현하는 주요 방법 중 하나입니다.
- Unity 예시: 모든 Unity 스크립트가
MonoBehaviour
라는 기본 클래스를 상속받아 생명주기 메서드(Start
,Update
등)나 코루틴 같은 Unity 엔진의 핵심 기능을 활용하는 것이 대표적인 상속의 예입니다. 또한, 게임 내 모든 적 캐릭터의 공통적인 특징(예:health
,speed
,TakeDamage()
메서드)을 가진Enemy
기본 클래스를 만들고, 이를 상속받는Goblin
,Dragon
,Slime
등의 구체적인 적 클래스를 만들어 각자의 고유한 특징을 추가하거나TakeDamage()
동작을 다르게 구현할 수 있습니다.
3. 다형성 (Polymorphism)
- 정의: 그리스어로 "많은 형태들(many forms)"을 의미하며, 프로그래밍에서는 하나의 인터페이스나 부모 클래스 타입의 참조 변수를 통해 다양한 자식 클래스 타입의 객체를 동일한 방식으로 다룰 수 있는 능력을 말합니다. 즉, 같은 이름의 메서드를 호출하더라도 객체의 실제 타입(런타임 타입)에 따라 각기 다른 구현이 실행되는 특성입니다. 이는 주로 메서드 오버라이딩(Overriding, 부모 클래스의
virtual
메서드를 자식 클래스에서override
하여 재정의)과 인터페이스 구현을 통해 달성됩니다. - 설명: 다형성을 사용하면 코드가 특정 자식 클래스 타입에 얽매이지 않고, 보다 일반적인 부모 타입이나 인터페이스를 기준으로 작성될 수 있습니다. 이를 통해 코드가 훨씬 유연해지고 확장이 용이해집니다.
- 이점:
- 코드 유연성 및 확장성 증대: 새로운 자식 클래스가 추가되더라도, 기존의 부모 타입 참조를 사용하는 코드는 수정 없이 새로운 타입을 처리할 수 있습니다 (OCP 원칙과 관련).
- 결합도 감소: 코드가 구체적인 구현 클래스가 아닌 추상적인 인터페이스나 부모 클래스에 의존하게 되어 컴포넌트 간의 결합도가 낮아집니다.
- 코드 간결화: 다양한 타입의 객체를 처리하는 로직을 하나의 통일된 방식으로 작성할 수 있습니다.
- Unity 예시: 여러 종류의 적(
Goblin
,Dragon
) 객체들을List<Enemy>
에 담아 관리한다고 가정해 봅시다. 이 리스트를 순회하며 각enemy
객체에 대해enemy.Attack()
메서드를 호출할 수 있습니다. 만약Enemy
클래스의Attack()
메서드가virtual
로 선언되었고,Goblin
과Dragon
클래스에서 이 메서드를override
하여 각자의 공격 방식을 구현했다면,enemy.Attack()
호출 시 실제 객체가Goblin
이면 고블린의 공격 방식이,Dragon
이면 드래곤의 공격 방식이 자동으로 실행됩니다. 코드는 단지Enemy
타입의Attack()
을 호출했을 뿐인데, 실제 동작은 객체의 타입에 따라 달라지는 것, 이것이 다형성입니다. 마찬가지로GetComponent<IInteractable>()
을 통해 얻은 참조로Interact()
를 호출하면, 실제 어떤 객체(NPC, 상자, 레버 등)가 반환되었는지에 따라 각기 다른 상호작용이 일어납니다.
4. 추상화 (Abstraction)
- 정의: 객체의 필수적인(Essential) 특징은 강조하여 외부에 드러내고, 불필요한 세부 구현 내용은 감추는 과정 또는 그 결과물을 의미합니다. 사용자는 객체의 내부 작동 원리를 자세히 알지 못해도, 외부에 공개된 인터페이스(메서드, 속성 등)를 통해 객체를 사용할 수 있어야 합니다.
- 설명: 추상화는 복잡성을 관리하는 핵심적인 기법입니다. C#에서는 주로 추상 클래스(Abstract Class) 와 인터페이스(Interface) 를 통해 구현됩니다. 추상 클래스는 일부 구현을 포함할 수 있는 미완성 설계도이며, 인터페이스는 구현 없이 오직 기능의 계약(메서드 시그니처, 속성 등)만을 정의합니다. 추상화는 "무엇(What)"을 하는지에 집중하고 "어떻게(How)" 하는지는 숨깁니다.
- 이점:
- 복잡성 감소: 사용자는 필요한 기능의 인터페이스만 알면 되므로 시스템의 복잡성이 줄어듭니다.
- 설계 유연성 증대: 구현 세부 사항이 변경되더라도 추상화된 인터페이스는 그대로 유지될 수 있어 변경의 파급 효과를 줄입니다.
- 역할과 구현의 분리: 시스템의 역할을 정의하는 부분과 실제 구현을 분리하여 설계의 명확성을 높입니다.
- Unity 예시: 게임 내 모든 "움직일 수 있는 유닛"의 공통 기능을 정의하기 위해
abstract class MovableUnit
을 만들고, 모든 유닛이 반드시 가져야 할abstract void MoveTo(Vector3 destination);
추상 메서드를 선언할 수 있습니다. 구체적인PlayerUnit
,EnemyUnit
클래스는MovableUnit
을 상속받아 각자의 방식으로MoveTo
메서드를 반드시 구현해야 합니다. 또는, "피해를 입을 수 있는" 모든 객체의 공통 계약으로interface IDamageable
을 정의하고void TakeDamage(int amount);
메서드 시그니처를 포함시킬 수 있습니다. 플레이어, 적, 파괴 가능한 환경 오브젝트 등 피해를 입을 수 있는 모든 클래스가 이 인터페이스를 구현하면, 외부 코드는 객체가IDamageable
인지 확인하고TakeDamage
를 호출하기만 하면 됩니다. 내부적으로 체력을 어떻게 감소시키고, 어떤 효과를 발생시키는지는 각 클래스의 구현에 맡겨집니다(캡슐화).
5. 4가지 특징의 상호 관계 및 시너지
캡슐화, 상속, 다형성, 추상화는 독립적인 개념이라기보다는 서로 긴밀하게 연관되어 객체 지향 설계의 강력함을 만들어냅니다.
- 캡슐화는 객체의 내부 상태를 보호하고 추상화는 객체의 필수 인터페이스를 외부에 노출시킵니다.
- 상속은 코드 재사용과 계층 구조를 가능하게 하며, 다형성은 이 상속 구조(또는 인터페이스 구현)를 활용하여 유연한 코드 작성을 지원합니다.
- 추상화(인터페이스, 추상 클래스)는 다형성을 실현하는 주요 수단이며, 상속은 추상화된 내용을 구체화하는 방법 중 하나입니다.
- 이러한 특징들을 올바르게 사용하는 데 도움을 주는 지침이 바로 앞서 다룬 SOLID 원칙입니다. 예를 들어, LSP는 올바른 상속과 다형성 활용을 강조하고, DIP는 추상화에 의존하도록 유도합니다.
6. Unity 개발에서의 OOP 특징 활용
Unity 개발에서 이 네 가지 특징은 다음과 같이 적극적으로 활용됩니다.
- 캡슐화: 컴포넌트 기반 설계 자체가 각 컴포넌트의 역할과 데이터를 캡슐화하려는 시도입니다.
private
필드와[SerializeField]
, 프로퍼티를 통해 컴포넌트 내부 상태를 보호하고 관리합니다. - 상속:
MonoBehaviour
상속은 필수적이며, 공통 기능을 가진 기본 클래스(Base Class)를 설계하여 코드 재사용성을 높입니다 (예:BaseCharacter
,BaseProjectile
). 하지만 너무 깊은 상속 계층은 피하고 구성을 선호하는 것이 좋을 때도 있습니다. - 다형성: 다양한 종류의 게임 오브젝트(적, 아이템, 상호작용 객체 등)를 공통 인터페이스(
IInteractable
,IDamageable
)나 부모 클래스(Enemy
) 참조를 통해 일관된 방식으로 처리하는 데 필수적입니다.GetComponent<InterfaceType>()
활용이 대표적입니다. - 추상화: 게임 시스템의 핵심 계약(예: 무기 시스템, 상태 머신, 인벤토리 시스템)을 인터페이스나 추상 클래스로 정의하여 시스템 간의 결합도를 낮추고 모듈성과 확장성을 확보합니다.
결론
캡슐화, 상속, 다형성, 추상화는 객체 지향 프로그래밍을 지탱하는 네 개의 기둥과 같습니다. 이 네 가지 핵심 특징을 이해하고 올바르게 활용하는 것은 Unity C#을 사용하여 복잡한 게임 시스템을 효과적으로 설계하고 구현하는 데 있어 필수적입니다. 캡슐화는 데이터와 행위를 안전하게 보호하고, 상속은 코드 재사용과 계층 구조를 제공하며, 다형성은 유연하고 확장 가능한 코드를 가능하게 하고, 추상화는 복잡성을 관리하고 핵심 개념에 집중하게 해줍니다. 이러한 OOP의 기본 원칙들을 꾸준히 학습하고 적용하며, SOLID 원칙과 같은 좋은 설계 지침을 따르려는 노력을 통해 개발자는 더욱 견고하고, 유지보수하기 쉬우며, 확장 가능한 고품질의 Unity 애플리케이션을 만들어 나갈 수 있을 것입니다.
참고 자료
- Microsoft C# 공식 문서 - 클래스 및 개체 지향 프로그래밍 섹션
- Unity 학습 사이트의 C# 및 OOP 관련 튜토리얼