이전에 공부한 조건문과 반복문, 배열과 컬렉션, 메서드와 구조체에 대해서 정리하고
3주차 강의 클래스와 객체, 상속과 다형성, 고급문법 및 기능을 수강 후 정리할 생각이다.
정리 리마인드
아래 노션 링크에 배웠던 개념을 정리하였다.
아래는 금일 정리한 개념이다.
클래스는 상속을 구현할 수 있다.
상속을 하면 오버라이딩이 가능하며 이 때 부모 클래스의 추상 메소드를 가져와 재구현한다.
또한 추상 클래스를 구현하여 상속 받은 클래스에게 오버라이딩도 가능하며
인터페이스를 통해 비슷한 작업이 가능하다.
여기서 각각의 특징과 정의, 사용 용도를 확실하게 짚고 넘어가고자 한다.
우선 클래스 상속부터 시작한다.
클래스의 상속은 한 클래스가 다른 클래스의 기능을 물려받는 것이다.
public class Animal
{
public void Breathe() => Console.WriteLine("숨 쉰다");
}
public class Dog : Animal
{
public void Bark() => Console.WriteLine("멍멍");
}
동물이라는 부모 클래스를 개라는 자식 클래스가 상속받아 부모 클래스의 멤버에 접근 가능하게 된다.
부모 클래스의 멤버에 접근 가능하다는 의미는
부모 클래스의 메소드를 사용할 수 있다는 것이고
자식 클래스에 맞게 메소드를 재정의하는 것이 오버라이딩이다.
public class Animal
{
public virtual void Speak() => Console.WriteLine("소리 낸다");
}
public class Dog : Animal
{
public override void Speak() => Console.WriteLine("멍멍");
}
여기서 주목할 점은
오버라이딩을 할려면 부모클래스의 메소드에서 virtual 키워드로 메소드를 정의해두어야한다.
그리고 자식 클래스에서 재정의할 때, override 키워드를 작성해야 한다.
이것이 다형성의 핵심 구현 방법이다.
오버라이딩의 구현은
virtual키워드로 부모 클래스의 오버라이딩 허용하고
override키워드로 자식 클래스에서 재정의한다.
다음은 추상 클래스이다.
public abstract class Weapon
{
public void Reload() => Console.WriteLine("장전!");
public abstract void Fire(); // 구현 X
}
public class Gun : Weapon
{
public override void Fire() => Console.WriteLine("탕!");
}
공통적인 기능은 구현하고, 구체적인 부분은 자식 클래스에 맡긴다.
이 때, 추상 클래스는 인스턴스화가 불가능 하다.
다음은 지금까지 살펴본 인터페이스다.
public interface IInteractable
{
void Interact();
}
//....
public class Door : IInteractable
{
public void Interact() => Console.WriteLine("문 열림");
}
구현할 메소드만 제공하는 구조이다.
여기까지 살펴보았다면,
점점 세부적인 구현에 있어서
하위 클래스에 맡기는 부분이 커짐을 알 수 있다.
표로 정리해서 한 눈에 살펴보자
| 구분 | 상속 | 오버라이딩 | 추상 클래스 | 인터페이스 |
|---|---|---|---|---|
| 목적 | 기능 재사용 | 기능 변경 | 공통 + 강제 구현 | 명세만 제공 |
| 키워드 | : | virtual / override | abstract | interface |
| 다중 구현 | ❌ | ❌ | ❌ | ✅ |
| 인스턴스 생성 | ✅ | ✅ | ❌ | ❌ |
| 구현 포함 | ✅ | ✅ | ✅ + ❌ 혼합 | ❌ |
| 자식 클래스 구현 강제 | ❌ | ❌ | ✅ (추상 메소드만) | ✅ (전부) |
특히 추상 클래스와 인터페이스는 언제 어떤 것을 사용할 지 모호한데,
공통 구현이 있고, 하나만 상속 가능하면 추상 클래스를,
명세만 필요하고 여러 역할을 조합(다중 상속)하려면 인터페이스를 사용하면 된다.
아래는 상황에 따라 정리한 표다.
| 상황 | 클래스 상속 | 추상 클래스 | 인터페이스 |
|---|---|---|---|
| 코드 재사용이 중요할 때 | ✅ | ✅ | ❌ |
| 공통 기능 제공 + 구현 강제 | ❌ | ✅ | ❌ |
| 구현 없이 규약만 정의하고 싶을 때 | ❌ | ❌ | ✅ |
| 다중 기능 조합이 필요할 때 | ❌ | ❌ | ✅ |
| 서로 다른 계열 클래스에 공통 기능 | ❌ | ❌ | ✅ |
| 일부 기능은 제공, 나머지는 위임 | ❌ | ✅ | ❌ |
오크, 고블린, 슬라임 등 다양한 몬스터를 구현한다고 생각해보자
이 몬스터들은 체력, 이동, 공격같은 기본적인 공통된 특징을 가진다
이 경우 Monster 라는 부모 클래스를 만들고
각 몬스터가 상속 받도록 구성할 수 있다.
관계에 대해 주목할 필요가 있는데,
오크는 몬스터다", "슬라임은 몬스터다" → is-a 관계라서
공통 코드 (Move(), TakeDamage(), Die())를 한 번만 구현하고,
필요한 기능만 추가하거나 오버라이드 하면 된다.
즉, 기본 기능만 물려줄 때 클래스-상속으로 구현한다.
FPS 게임을 만들 때, 다양한 무기가 있을 것이다
모든 무기는 장전기능이 있지만
발사 방식은 다를 수 있다.
즉, 기본적인 특징 중에서 공통된 것도 있지만 다를 수 있는 것이 있는 경우다.
이 경우 공통된 메서드는 추상 클래스에서 제공하고
다른 부분만 자식이 반드시 구현하도록 강제한다.
Weapon (abstract class)
├── Gun → 탄창 방식 발사
├── Bow → 차징 방식 발사
└── Laser → 오버히트 방식 발사
공통 로직 (장전)을 제공하면서
중요한 핵심 기능은 각 무기마다 다르게 구현할 수 있다.
즉, 공통 기능에 각각에 맞게 구현해야하는 기능이 있는 경우 추상 클래스를 사용한다.
문, 버튼, 상자, NPC 등등 여러 오브젝트가 있고,
플레이어는 가까이 가서 상호작용을 하는 게임을 구현한다고 생각해보자.
NPC는 대화를 시작하고
문은 열리고
상자도 열리고
버튼은 무언가를 작동시킬 것이다.
이 오브젝트들은 전혀 다른 계열이지만
상호작용이 가능하다라는 공통점이 있다.
이 때 쓰는 것이 인터페이스이다.
IInteractable 인터페이스
├── NPC → 대화
├── Door → 문 열기
└── Switch → 레버 당기기
추상 클래스와 인터페이스를 같이 사용할 수 있을까?
추상 클래스와 인터페이스를 섞어 설계할 수 있다면 어떤 구조가 나올까?
결론부터 말하면,
추상 클래스는 “공통 기반”, 인터페이스는 “기능 역할”로 나누어 함께 조합하는 것이 가능하고 이상적인 설계이다.
이 두 가지는 역할이 다르기 때문에 서로 충돌하지 않고
오히려 함께 쓰면 더 강력한 설계가 가능하다.
예를 들어 RTS 게임의 유닛 시스템을 만든다고 가정해보자.
유닛은 공통된 기능이 있고, 유닛마다 가능한 역할도 다양할 것이다.
공통 기능으로는 이동, 체력이 있을 것이고
역할 기능으로는 공격과 상호작용이 있을 것이다.
그렇다면 공통 기능은 추상클래스로, 역할 기능은 인터페이스로 설계해보자
추상 클래스에는 공통된 기능을 구현하는 멤버와 유닛별 커스터마이징 포인트는 추상 메소드로 설계 가능하다.
인터페이스는 각 유닛의 역할을 분리하여 설계하면 될 것이다.
공통기능 추상 클래스
{
public 체력, 이동...
public abstract void Die(); // 유닛마다 죽을 때 다름
}
//.....
역할 기능 인터페이스
공격 역할 인터페이스
{
공격메소드;
}
채집역할 인터페이스
{
채칩처리메소드;
}
이런 식으로 설계할 수 있다.
정리하자면
오늘은 상속, 추상 클래스, 인터페이스에 대해 확실히 정리할 수 있었고, 게임 설계에서 어떻게 활용되는지 구체적인 예시를 통해 명확해졌다.
추상 클래스는 공통 기능의 뼈대, 인터페이스는 역할의 조합이라는 구분이 명확해지면서 개발에 필요한 프레임워크(?)가 잡혀가는 것 같았다.
나중에 프로젝트를 진행하면서 오늘 정리한 내용이 체화되어는지, 훗날 다시와서 피드백을 작성할 날을 기대한다.
개인 프로젝트 TextRPG를 가볍게 진행했었는데, 벨로그에 정리할 시간이 없어 노션에 일단 기록 중에 있다.
TextRPG_SPartaDungeon