object는 값 타입(int, float, bool 등)을 참조 타입인 object로 담을 때 박싱(Boxing) 이 발생하고, 다시 원래의 값 타입으로 꺼낼 때는 언박싱(Unboxing) 이 발생해.
int num = 10;
object obj = num; // 박싱 (값 타입 → object)
int result = (int)obj; // 언박싱 (object → 원래 값 타입)
📌 박싱과 언박싱은 성능에 영향을 줄 수 있기 때문에 주의해서 사용해야 해.
✅ 형변환 주의사항
object 타입으로 캐스팅된 값은 원래 타입을 정확히 알아야 다시 사용할 수 있어.
object obj = "Hello";
// 안전한 캐스팅
if (obj is string str) {
Console.WriteLine(str.ToUpper());
}
// 또는 as 키워드
string? maybeStr = obj as string;
if (maybeStr != null) {
Console.WriteLine(maybeStr.ToUpper());
}
✅ 실전 예시
void HandleAnything(object input) {
if (input is int i) {
Console.WriteLine($"정수: {i}");
} else if (input is string s) {
Console.WriteLine($"문자열: {s}");
} else {
Console.WriteLine("기타 타입");
}
}
✅ 요약 정리
항목
타입
object
모든 타입의 최상위 타입 (C#의 루트)
사용 목적
범용 처리, 다형성 구현
박싱
값 타입 → 참조 타입으로 변환
언박싱
참조 타입 → 값 타입으로 꺼냄
주의점
박싱/언박싱은 성능 저하를 유발할 수 있음
❓
오브젝트 타입은 모든 타입의 최상위 타입이고 타입중의 최상위 클래스네여 별로 어려운 부분은 없는 듯 합니다.
근데 이제 궁금한건 메모리에 관해서 어떻게 작동하는지가 궁금하네여 박싱/언박싱이 이루어진다면
주소값을 스택에 저장하고 힙에 데이터를 저장할텐데 말이죠! 한번 물어보겠습니다!
✅ C# 메모리 구조 간단 정리
C#은 주로 두 가지 메모리 영역을 사용해:
스택(Stack): 값 타입 저장 (작고 빠름)
힙(Heap): 참조 타입 저장 (크고 유연함, 가비지 컬렉션 대상)
✅ 값 타입과 참조 타입의 메모리 동작 차이
구분
값 타입 (int, float, struct)
참조 타입 (class, string, object)
저장 위치
스택
힙 (스택엔 참조 주소)
저장 내용
실제 값
힙 주소 (레퍼런스)
복사 시
값이 복사됨
참조(주소)가 복사됨
✅ object와 메모리 – 박싱(Boxing)
💡 예시:
int x = 10;
object obj = x; // 박싱
📌 박싱 시 메모리 흐름
x는 스택에 저장돼 (int는 값 타입)
obj = x 하면,
힙에 새로운 int 값을 저장할 공간을 만들고
x 값을 거기에 복사해
obj는 그 힙 공간을 가리키는 참조를 가지게 돼
즉, 값 타입이 힙 객체로 변환되면서 메모리 복사 + 힙 할당이 발생하는 거야.
✅ 언박싱(Unboxing) – 다시 스택으로
int y = (int)obj; // 언박싱
📌 언박싱 시
obj가 가리키고 있는 힙의 값을 다시 스택으로 복사
원래 타입(int)으로 변환해줌
이 과정 역시 비용이 발생해.
✅ 박싱/언박싱 메모리 시각화
int x = 10; // 스택: [x = 10]
object obj = x; // 힙: [10], 스택: [obj -> 힙주소]
int y = (int)obj; // 스택: [y = 10]
✅ 참고: object에 참조 타입을 넣는 경우
string s = "hello";
object obj = s;
이건 박싱이 발생하지 않음. 왜냐면 string은 이미 참조 타입이기 때문에, 단순한 참조 복사만 일어나. 📌 박싱은 오직 "값 타입 → object"로 바뀔 때만 발생함.
✅ 요약
object는 참조 타입
값 타입을 넣으면 힙에 복사되고 주소가 저장됨
박싱
스택 값 → 힙에 새로 저장 + 주소 저장
언박싱
힙 값 → 스택에 복사 + 형변환
성능 주의
박싱/언박싱은 힙 할당 + 복사로 성능 저하 가능
참조 타입은?
박싱 안됨. 그대로 주소 복사
글로만 보니까 조금 이해하기 힘들수도있을듯해서 친구 블로그에서 그림을 가져왔습니다ㅎㅎ
그림으로 보면 더 이해하기 쉬울듯 해서요!
✅
확실히 그냥 int a = 5 일때는 스택에 값을 저장하지만
object a = 5는 스택에 a가 주속값을 가지고 있고 힙에 값을 저장합니다.
그리고 int b = (int)a를 했을때 b는 힙에있는 5의 값을 저장하고 힙에있는 데이터 값을 저장하게되어
힙에 남아있는 5는 가비지가 되어 C#의 GC가 실행되어 없어질듯합니다.
✅ 정리: 각 단계에서 메모리 동작
1️⃣ int a = 5;
a는 값 타입 → 스택에 5 저장
✅ 정확함
stack:
└─ a = 5
heap:
└─ (비어 있음)
2️⃣ object a = 5; → 박싱(Boxing)
5는 값 타입이지만 object는 참조 타입이므로,
값 5가 힙에 복사되어 저장되고,
스택의 a는 힙 주소값을 가짐
✅ 정확함
stack:
└─ a = 힙 주소
heap:
└─ [값 5]
3️⃣ int b = (int)a; → 언박싱(Unboxing)
힙에 있던 5를 다시 값 타입(int) 으로 스택에 복사
b는 스택에 5를 가지며 독립적인 값이 됨
stack:
└─ b = 5
heap:
└─ [값 5] (여전히 존재)
📌 여기서 중요한 점은:
언박싱을 해도 힙의 기존 object는 여전히 존재해
단, 더 이상 참조되지 않는다면, C#의 GC(Garbage Collector) 가 언젠가 메모리에서 회수함
✅ "가비지가 된다"는 표현도 정확함!
🔍 보완 포인트
❗ 힙에 있는 값 = 가비지가 즉시 발생하지는 않음
언박싱 후에도 여전히 object a가 해당 힙 메모리를 참조하고 있다면 GC 대상이 아님
object a가 스코프 밖으로 사라지거나 null로 변경되어야 참조가 끊김 → 그때 GC 대상이 됨
부모 타입으로 자식 객체를 참조해도, 메모리는 자식 전체가 생성되며, 실행 시 다형성에 따라 자식 메서드가 호출된다.
❓
다형성이 제대로 이해되지 않는다 흠.. 다시 한번 물어보자.
좋아, 이제 진짜 핵심 중의 핵심으로 왔어. 많은 사람이 객체지향에서 **“다형성(polymorphism)”**이라는 단어를 듣고 **“뭔가 중요한 것 같긴 한데… 솔직히 감이 안 잡혀”**라고 느끼거든. 지금 딱 그런 시점에 온 거야 — 지금부터 진짜 이해되는 방식으로 다형성을 설명해줄게 👇
✅ 다형성이란?
"같은 타입(부모 타입)으로 여러 객체(자식 객체)를 같은 방식으로 다루지만, 실제로는 각 객체에 맞는 동작이 실행되는 것"
🔥 핵심 포인트
"부모 타입 하나로 다양한 자식 객체를 처리한다." → 다형성
다형성은 “형태는 같지만 행동은 다르게” 동작함
🎯 예제로 설명해볼게
class Animal
{
public virtual void Speak()
{
Console.WriteLine("동물이 소리를 냅니다.");
}
}
class Dog : Animal
{
public override void Speak()
{
Console.WriteLine("멍멍!");
}
}
class Cat : Animal
{
public override void Speak()
{
Console.WriteLine("야옹!");
}
}
List<Animal> animals = new List<Animal>();
animals.Add(new Dog());
animals.Add(new Cat());
foreach (Animal a in animals)
{
a.Speak(); // ??? 어떤 소리가 나올까?
}
👉 출력 결과:
멍멍!
야옹!
🔍 여기서 중요한 건 → a.Speak()는 전부 Animal 타입인데, 실제로는 Dog, Cat 각각의 메서드가 실행된다!
➡️ 이게 바로 다형성(polymorphism) ➡️ 같은 코드(a.Speak()) → 다양한 동작
🧠 비유로 이해해보기
부모 타입 = 콘센트 자식 객체 = 다양한 기기 (선풍기, 에어컨, 충전기 등)
콘센트 하나(Animal 변수)로 어떤 기기(Dog, Cat)를 꽂든,
각각의 기기는 자기 기능대로 작동
콘센트.작동() →
→ 선풍기면 바람 나옴
→ 충전기면 충전됨
→ 에어컨이면 냉풍 나옴
➡️ 인터페이스는 같고, 동작은 다름 ➡️ 다형성!
✅ 왜 중요한가?
이유
설명
유지보수가 쉬워짐
부모 타입 기준으로 다룰 수 있어서 코드가 유연해짐
코드 중복 줄임
동일한 인터페이스/부모 클래스로 묶을 수 있음
확장성 좋아짐
새로운 자식 클래스가 생겨도 부모 타입만 알면 됨
🎮 게임 예제
abstract class Enemy
{
public abstract void Attack();
}
class Slime : Enemy
{
public override void Attack() => Debug.Log("슬라임 점프 공격!");
}
class Dragon : Enemy
{
public override void Attack() => Debug.Log("드래곤 불뿜기!");
}
List<Enemy> enemies = new List<Enemy>() {
new Slime(), new Dragon()
};
foreach (Enemy e in enemies)
{
e.Attack(); // 각 몬스터의 고유 공격 실행됨!
}
✅ 한 줄 정리
다형성은 **“하나의 타입(부모)으로 여러 타입(자식)을 다루면서도, 각 객체에 맞는 동작을 실행하는 객체지향의 핵심 기능”**이야.
✅
그렇다고한다! 아직도 와닿지는 않지만 이해는 됐다. 직접 개발해봐야 느낌이 올듯하다. 확실히 나중에 개발하면 코드에 대한 유지보수나 여러 기능들 만들고 사용할때 많이 사용하니 잘 알아둬야 할듯하다.
class Parent
{
public void Speak()
{
Console.WriteLine("부모가 말한다");
}
}
class Child : Parent
{
public void Say()
{
base.Speak(); // 부모의 Speak() 호출
}
}
📌 2. 부모 생성자 호출
class Parent
{
public Parent(string name)
{
Console.WriteLine($"{name} 생성됨");
}
}
class Child : Parent
{
public Child() : base("자식이지만 부모도 초기화")
{
Console.WriteLine("자식 생성자 실행");
}
}
📌 3. 오버라이드한 메서드 안에서 부모의 원본 호출
class Character
{
public virtual void Attack()
{
Console.WriteLine("기본 공격!");
}
}
class Warrior : Character
{
public override void Attack()
{
base.Attack(); // 부모의 기본 공격도 같이 실행됨
Console.WriteLine("검 휘두르기!");
}
}
💡 언제 쓰는가?
부모의 로직을 확장하고 싶을 때
base.메서드() 호출
부모의 생성자 초기화가 필요한 경우
: base(...) 사용
부모의 기본 동작도 유지하고 싶을 때
오버라이드 안에서 base 사용
⚠️ 주의
base는 상속 관계에서만 사용 가능
자식이 부모를 가리킬 때만 쓸 수 있음
base는 부모 클래스의 "구현된 기능"에 접근할 때 쓰는 거지, 추상 인터페이스에는 직접 쓰지 않음
✅ 한 줄 정리
base는 자식 클래스에서 "부모 클래스의 기능을 호출"할 수 있도록 해주는 키워드야.
❓
흠..잘 알겠지만 이건 잘 사용하지 않을듯한 느낌이 든다.. 아직 내가 유니티에서 사용하지 않아봐서 그럴지도 모른다.
그래서 GPT한테 base 키워드에한 중요성을 물어봤다!
진짜 좋은 질문이야. 많은 사람이 처음에 base를 보면 “어? 굳이 이걸 꼭 써야 하나?”, “내가 override 해서 다 재정의하면 되지 않나?” 이런 생각을 해. 근데 실제로는 잘 쓰면 엄청 유용하고, 안 쓰면 부모 클래스 기능을 날려먹을 수 있어.
✅ base의 중요성은 “부모 기능을 이어받되, 추가하거나 확장할 수 있게 해주는 것”이야.
🎯 base를 안 쓰면 생기는 문제
예시: 캐릭터 기본 공격을 무시해버림
class Character
{
public virtual void Attack()
{
Console.WriteLine("기본 공격!");
}
}
class Warrior : Character
{
public override void Attack()
{
Console.WriteLine("워리어 스킬 발동!");
}
}
👉 이 경우 기본 공격 출력 안 됨
Character의 공격 로직이 완전히 무시됨 → 부모 기능 상실
✅ base를 써서 부모 기능도 유지
class Warrior : Character
{
public override void Attack()
{
base.Attack(); // 부모의 기본 공격 먼저 실행
Console.WriteLine("워리어 스킬 발동!");
}
}
➡️ 결과:
기본 공격!
워리어 스킬 발동!
✅ → 부모 로직 + 자식 로직이 순차적으로 실행됨 = 기능 확장
🔥 실전에서 base를 잘 쓰는 대표적인 상황
UI 초기화, 애니메이션 제어
부모가 기본 처리 다 해놓고, 자식은 추가 효과만 넣고 싶을 때
MonoBehaviour의 Start(), Update() 재정의
자식에서 base.Start() 호출 안 하면 부모의 Start 로직이 사라짐
디버깅, 추적, 로깅
부모에서 로깅 다 해놓고, 자식이 그 위에 로직 덧붙일 수 있음
템플릿 메서드 패턴
부모의 흐름 유지하면서 일부만 바꿔야 할 때 base로 확장 가능
❗ 안 쓰면 벌어지는 일
부모 클래스의 중요한 로직이 사라짐
자식 클래스가 전체 흐름을 깨뜨려버림
유지보수할 때 “왜 안 되지?”하고 원인을 찾기 어려워짐
✅ 한 줄 정리
base는 **"부모의 로직을 살리면서 자식에서 기능을 확장"**하고 싶을 때 정말 중요한 도구야.
✅
오 그래도 이렇게 보니까 base의 기능이나 필요성을 잘 알거같다 나중에 유니티 개발할때 꼭 사용해봐야겠다!
**템플릿 메서드 패턴(Template Method Pattern)**은 상속을 기반으로 부모 클래스에서 **알고리즘의 전체 흐름(틀)**을 정의하고, 일부 단계는 abstract 또는 virtual로 정의하여 자식 클래스가 자유롭게 재정의할 수 있도록 하는 패턴입니다.
공통 기능 (Idle(), Move())은 virtual, 꼭 구현해야 하는 핵심 기능은 abstract로 정의
구체 클래스
Warrior, Magician
PlayerBase를 상속받아 필요한 메서드들을 override
클라이언트
Test
PlayerBase 타입으로 실제 자식 인스턴스를 받아 실행 (다형성 활용)
🔍 코드 구조 분석
1. 템플릿 클래스: PlayerBase
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace Template
{
// 템플릿 클래스
// 공통 기능(Idle, Move)은 virtual로 제공하고
// 핵심 기능(MoveTarget, Attack)은 abstract로 정의해 자식이 반드시 구현하도록 강제함
public abstract class PlayerBase : MonoBehaviour
{
public virtual void Idle()
{
// 공용 Idle 구현
}
public virtual void Move()
{
// 공용 Move 구현
}
public abstract void MoveTarget(); // 반드시 자식이 구현해야 할 핵심 로직
public abstract void Attack(); // 반드시 자식이 구현해야 할 핵심 로직
}
}
공통 동작은 virtual로 정의하여 자식이 선택적으로 재정의
핵심 필수 동작은 abstract로 정의하여 자식이 반드시 구현하게 강제
2. 구체 클래스: Warrior, Magician
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace Template
{
// Warrior 클래스는 PlayerBase를 상속받아
// 추상 메서드를 구체적으로 구현한다
public class Warrior : PlayerBase
{
public override void MoveTarget()
{
// 워리어 MoveTarget 구현
// abstract로 받아서 직접 구현해줘야함
}
public override void Attack()
{
// 워리어 Attack 구현
// abstract로 받아서 직접 구현해줘야함
}
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace Template
{
// Magician 클래스는 PlayerBase를 상속받고
// Move()까지 override해서 고유 이동 방식 정의
public class Magician : PlayerBase
{
public override void Move()
{
// 메지션 Move 구현
// 메지션은 따로 움직임이 필요해서 만든 것
}
public override void MoveTarget()
{
// 메지션 MoveTarget 구현
// abstract로 받아서 직접 구현해줘야함
}
public override void Attack()
{
// 메지션 Attack 구현
// abstract로 받아서 직접 구현해줘야함
}
}
}
Warrior는 기본 Move() 로직 사용 → override 안함
Magician은 이동이 특이하므로 Move()도 override
3. 실행 코드 (클라이언트): Test
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace Template
{
public class Test : MonoBehaviour
{
[SerializeField] PlayerBase _playerBase;
private void Start()
{
// Idle
_playerBase.Idle();
// Move
_playerBase.Move();
// MoveTarget
_playerBase.MoveTarget();
// Attack
_playerBase.Attack();
// 사용한다면 이런식으로 사용한다.
// 다양성을 이용해서 만들어놓은 워리어, 메지션 등 사용할 수 있다.
// => 이게 바로 템플릿 메서드 패턴의 핵심! 알고리즘 틀은 고정, 세부는 다양
}
}
}
PlayerBase 타입 하나로 Warrior, Magician 등 다양한 자식 타입을 다룰 수 있음
다형성(Polymorphism) 활용 → 코드 변경 없이 캐릭터 전환 가능
🎮 정리: 왜 템플릿 메서드 패턴이 좋은가?
장점설명
알고리즘의 흐름 고정
공통 로직은 부모가 제공하여 일관성 유지
세부 단계 유연화
자식 클래스가 필요한 부분만 재정의 가능
중복 제거
공통 코드 반복을 줄이고 관리 포인트를 줄임
다형성 활용
PlayerBase 하나로 모든 캐릭터를 통합해서 제어 가능
✅ 최종 요약
PlayerBase는 게임 캐릭터 로직의 골격을 정의한 템플릿 클래스
Warrior, Magician은 세부 구현을 담당하는 서브 클래스
Test는 추상 타입을 통해 다양한 캐릭터를 처리하는 클라이언트
abstract, virtual을 적절히 활용하여 강제성과 유연성의 균형을 유지
❓
음..사실 템플릿 메서드가 영상을 보고도 왔지만 정확히 이해가 잘 되지 않았다 템플릿을 왜 쓰는걸까? 그냥 virtual이랑 abstract를 사용해서 상속받고 정의하고 뭐 이런느낌인거같은데 템플릿을 왜 쓰는지 정확히 이해가 되지 않았다.
그래서 GPT 에게 다시 물어봤다 왜쓰는지.
✅ 템플릿 메서드는 왜 쓰는 걸까?
"전체 알고리즘의 흐름은 고정하되, 일부 단계는 유연하게 바꿀 수 있도록 하기 위해"
🎯 예시로 풀어보자
게임에서 "캐릭터 행동"이라는 공통 흐름이 있다고 해봐요:
1. Idle 상태 진입
2. 이동 처리
3. 타겟 탐색
4. 공격 처리
이 흐름은 모든 캐릭터가 공유하는 로직이에요. 하지만 각각의 캐릭터는 공격 방식이나 타겟 찾는 방식이 다를 수 있죠?
전사는 근거리에서 찌르고
궁수는 원거리에서 조준하고
마법사는 주문을 시전하고
이때 쓰는 게 바로 템플릿 메서드 패턴이에요.
✅ 정리하자면, 템플릿을 쓰는 이유는?
공통 흐름을 재사용하고 싶어서
알고리즘 틀은 매번 새로 만들 필요 없이 재사용 가능
세부 로직만 바꾸고 싶어서
자식 클래스가 필요한 부분만 override 하면 됨
중복 코드를 줄이고 싶어서
부모에 공통 로직을 두면 중복이 줄어듦
확장성 좋고 깔끔한 구조 만들기 위해
새로운 캐릭터, 새로운 동작 추가 시 최소한의 코드만 작성하면 됨
💬 템플릿을 안 쓰면 어떤 문제가 생길까?
class Warrior
{
public void DoAll()
{
Idle();
Move();
FindTarget(); // 전용
Attack(); // 전용
}
}
class Magician
{
public void DoAll()
{
Idle();
Move();
FindTarget(); // 전용
Attack(); // 전용
}
}
➡️ 공통 흐름이 중복됨 ➡️ 흐름을 바꾸려면 모든 클래스 수정해야 함 ➡️ 버그 생기고 유지보수 어려워짐
✅ 템플릿은 이렇게 정리할 수 있어요
템플릿 메서드 패턴은 "전체 알고리즘 흐름은 부모 클래스가 정하고, 그 일부 단계를 자식 클래스가 유연하게 바꿀 수 있도록 만드는 패턴"
그러면...그냥 오버 버츄얼로 오버라이드해서 사용하면 되는거 아닌가? 라는 생각이 들었다.
“그냥 virtual 메서드로 오버라이드해서 쓰면 되는데, 굳이 템플릿 메서드 패턴을 왜 따로 써야 해?”
✅ 결론부터 말하자면:
단순히 virtual로 메서드 하나하나 오버라이드하는 것과 "템플릿 메서드 패턴"을 적용하는 것의 차이는 알고리즘 전체 흐름을 ‘고정’하고 ‘설계 의도를 명확히 하는가’에 있어요.
🎯 차이점 설명
virtual 단독 오버라이드
각 기능을 개별적으로 바꿔치기만 가능 (공통된 흐름 없음)
템플릿 메서드 패턴
전체 흐름은 부모가 고정하고, 일부 단계만 자식이 커스터마이징
📌 예를 들어보자
① 그냥 virtual 방식
class Player
{
public virtual void Move() { /* 기본 이동 */ }
public virtual void Attack() { /* 기본 공격 */ }
}
class Warrior : Player
{
public override void Move() { /* 워리어 이동 */ }
public override void Attack() { /* 워리어 공격 */ }
}
➡️ 자식은 알아서 각자 필요한 것만 바꿈 ❌ 하지만 **"이 둘을 어떤 순서로 실행해야 하는지"**에 대한 틀이 없음
class Character
{
public virtual void Attack()
{
Console.WriteLine("기본 공격!");
}
}
class Warrior : Character
{
public override void Attack()
{
Console.WriteLine("칼 휘두르기!");
}
}
Character c = new Warrior();
c.Attack(); // 출력: 칼 휘두르기!
➡️ 부모 타입으로 자식 인스턴스를 다룰 수 있다 = 다형성
⚠️ 단점
단일 상속만 가능: 부모는 한 명만
상속 깊이가 깊어지면 유지보수가 어려워짐
상속 관계가 강한 결합을 만들 수 있음 (부모 변경 시 자식에 영향 큼)
✅ 인터페이스 (Interface)
✔️ 정의
interface IAttackable처럼, 기능의 "약속"만 정의하는 타입 → 구현 없이 메서드 시그니처만 선언 → 구현은 클래스에서 직접 해야 함
✔️ 목적
다형성 지원: 특정 인터페이스를 구현한 객체들을 동일하게 다룸
의존성 분리: 구체 타입보다 "역할"에 의존
다중 상속 가능: 하나의 클래스가 여러 인터페이스를 동시에 구현 가능
✔️ 예제
interface IAttackable
{
void Attack();
}
class Archer : IAttackable
{
public void Attack()
{
Console.WriteLine("화살 발사!");
}
}
IAttackable a = new Archer();
a.Attack(); // 출력: 화살 발사!
➡️ 누구든 IAttackable만 구현하면 같은 방식으로 다룰 수 있음 → 유연함, 약한 결합
✅ 클래스 상속 vs 인터페이스 정리
항목
클래스 상속
인터페이스
목적
기능 물려주기 (재사용)
역할 정의 (규칙 강제)
코드 재사용
✅ 가능
❌ 없음 (직접 구현 필요)
다중 상속
❌ 안 됨
✅ 가능
구현 제공
✅ 가능 (virtual, 필드 등)
❌ 없음 (단순 시그니처)
추상화 수준
구체적 (기능 포함)
완전 추상적
설계 유연성
낮음 (강한 결합)
높음 (약한 결합)
❓
그럼 클래스의 abstract와 인터페이스는 좀 비슷한거 아니야? 라고 생각이 들었다.
💯 딱 그거야! 많은 사람들이 처음에 “abstract class랑 interface… 비슷한데 뭐가 달라?” 라고 궁금해해. 왜냐면 둘 다 “강제 구현”을 요구하고, 직접 인스턴스를 만들 수 없고, 자식이 override 해야 하니까 헷갈릴 수 있어.
근데! 실제로는 의도와 사용 목적이 분명하게 다르기 때문에
**"언제 abstract class를 쓰고 언제 interface를 쓰는가?"**가 중요한 핵심이야.
✅ 공통점
인스턴스 생성 불가
abstract class, interface 모두 직접 new로 못 만듦
메서드 강제 구현
자식 클래스가 반드시 override해야 하는 메서드 있음
다형성 지원
부모 타입으로 자식 인스턴스를 다룰 수 있음 (Animal a = new Dog())
⚔️ 차이점
항목
abstract class
interface
목적
공통 로직 공유 + 일부 구현 강제
**기능 명세(역할)**만 정의
기본 구현 제공
✅ 가능 (필드, 메서드, 속성 등)
❌ 원칙상 불가능 (C# 8부터 일부 가능하긴 함)
필드 선언
✅ 가능
❌ 불가능
생성자
✅ 가질 수 있음
❌ 없음
상속 개수
❌ 단일 상속만 가능
✅ 다중 상속 가능
접근 제한자
✅ 사용 가능 (protected, private, 등)
❌ 기본적으로 public only
🎮 메이플스토리 비유로 다시 정리!
abstract class
공통 기능 + 각 직업 전용 스킬: 기본 체력 시스템, 레벨업 시스템 등 공유됨 + 각 전직 클래스가 고유 스킬 구현
interface
공격 가능한 존재: 플레이어, 몬스터, 보스가 각각 다른 방식으로 Attack() 구현함 → 역할만 공유함
✅ 언제 abstract class를 쓰고 언제 interface를 써?
공통 로직도 공유해야 함
✅ abstract class
여러 클래스가 같은 기능을 수행하지만, 내부 로직은 각자 다르게 하고 싶을 때
✅ interface
여러 개의 기능(역할)을 동시에 구현해야 함
✅ interface
기본 멤버/필드/생성자도 물려줘야 함
✅ abstract class
🧠 기억하기 좋은 한 줄 요약
interface는 “무엇을 할 수 있는가”를 말하고, abstract class는 “무엇을 공유할 것인가”를 말한다.
✅
흠.. 클래스 상속과 인터페이스는 알겠는데 아직까지 솔직히 abstract Class와 Interface의 차이가 이론적으로는 알겠지만 마음으로 와닿지 않는다 이건 직접 유니티로 구현해보면서 경험을 쌓는 수 밖에 없을 듯 하다..
템플릿 메서드 패턴에서 일부 단계를 virtual로 두면 자식 클래스가 필요 시 바꿀 수 있음
❗ 주의
virtual 메서드는 자식 클래스에서 override 키워드로 재정의해야 함
virtual 없이 그냥 override는 불가능함 → 부모가 virtual 또는 abstract여야 함
❓
그럼 궁금한게 꼭 버츄얼을 써야 오버라이드가 가능한건가? 싶기도하고 abstract는 또 뭘까 싶었다.
✅ 1. 꼭 virtual을 써야 override가 가능한가?
👉 네, 맞습니다.
override 키워드는 반드시 부모 클래스에서 virtual, abstract, 또는 override로 선언된 메서드만 재정의할 수 있어요.
즉, virtual 없이 그냥 만든 메서드는 자식 클래스에서 override 할 수 없습니다.
class Parent
{
public void Speak() // virtual이 아님
{
Console.WriteLine("부모가 말합니다");
}
}
class Child : Parent
{
// ❌ 오류! override 불가능
public override void Speak()
{
Console.WriteLine("자식이 말합니다");
}
}
✅ 2. 그럼 abstract는 뭐야?
👉 abstract는 부모 클래스에서 "이건 반드시 자식 클래스가 직접 구현해야 해!" 라고 강제할 때 사용해요.
📌 특징 비교
구분
virtual
abstract
기본 구현
✅ 있음
❌ 없음 (구현 금지)
재정의 여부
자식 클래스에서 재정의 선택 가능
자식 클래스에서 반드시 override 해야 함
용도
"기본 구현은 있지만, 필요하면 바꿔 써도 돼"
"이건 자식이 반드시 구현해야 돼"
🎯 코드 예시로 완전 비교
🔹 virtual 예시
class Parent
{
public virtual void Greet()
{
Console.WriteLine("Hello from Parent");
}
}
class Child : Parent
{
public override void Greet()
{
Console.WriteLine("Hello from Child");
}
}
🔹 abstract 예시
abstract class Animal
{
public abstract void Speak(); // 구현 ❌
}
class Dog : Animal
{
public override void Speak()
{
Console.WriteLine("멍멍!");
}
}
→ Speak()는 무조건 자식이 override 해야 함. → 안 하면 컴파일 오류 발생
🧠 요약 정리
질문답변
virtual 없이 override 가능?
❌ 불가능 (virtual, abstract, 또는 override 필요)
abstract는 뭐야?
자식 클래스가 반드시 구현해야 하는 메서드를 정의할 때 사용
virtual과 abstract 차이는?
기본 구현 유무 + 오버라이드 강제 여부의 차이
❓
그러면 굳이 abstract를 써야하는 이유가있나?..싶었다..
그럼 도대체 두개의 차이점이 뭘까..
✅ 핵심 요약: 둘의 의도와 차이
항목
virtual
abstract
기본 구현
있다 → 자식이 그대로 써도 됨
없다 → 자식이 반드시 구현해야 함
자식에서 override
선택 가능
강제
설계 의도
"기본 동작이 있지만, 필요하면 바꿔 써"
"이건 네가 알아서 반드시 만들어"
목적
유연성
강제성, 명확한 계약
사용 가능 클래스
일반 클래스, 추상 클래스
추상 클래스에서만 사용 가능
장점
코드 재사용 가능
설계 실수 방지, 역할 명확
단점
재정의 안 하면 예측 어려움
반복적 구현 필요 (유지비 증가)
🎯 예시 비교로 감 잡기
✅ 예제 상황: "게임 캐릭터"
1. virtual 예시
class Character
{
public virtual void Attack()
{
Console.WriteLine("기본 공격: 펀치!");
}
}
class Knight : Character
{
public override void Attack()
{
Console.WriteLine("검으로 공격!");
}
}
class Villager : Character
{
// Attack() 안 바꿔도 됨 → 기본 공격 그대로 사용
}
➡️ Villager는 특별한 공격이 없으니 virtual 덕분에 기본 공격을 그대로 써도 괜찮음 ➡️ 유연함이 중요할 때 → virtual
2. abstract 예시
abstract class Character
{
public abstract void Attack(); // 반드시 구현!
}
class Knight : Character
{
public override void Attack()
{
Console.WriteLine("검으로 공격!");
}
}
class Villager : Character
{
// ❌ Attack() 안 만들면 컴파일 에러 → 강제
}
➡️ 모든 캐릭터는 반드시 자신만의 공격을 가져야 해! → 이런 명확한 룰을 강제할 때 abstract ➡️ 설계 실수 방지하고 싶을 때 → abstract
📌 그럼 뭐가 더 효율적인가?
👉 "효율"은 상황에 따라 달라요.
기본 동작은 있지만, 바꿀 수 있도록 열어두고 싶다
✅ virtual
꼭 자식이 직접 구현해야만 의미가 있다
✅ abstract
공통 로직은 부모에서 쓰고, 일부만 다르게 처리하고 싶다
✅ 템플릿 메서드 + abstract + virtual 혼용
🧠 결론
virtual → "기본은 줄게, 필요하면 바꿔 써" → 재사용 중심, 유연한 구조
abstract → "이건 네가 반드시 해" → 계약 기반 설계, 실수 방지, 역할 명확화
🎯 기억하기 쉽게 요약하면:
"바꿔도 되고 안 바꿔도 되면 virtual, 무조건 바꿔야 한다면 abstract"
✅
음...정확하게 뭔가 와닿지는 않지만 캐릭터의 스킬이나 공격스킬로 따지자면
virtual - 기본 공격 스타일 메이플의 기본공격정도이지 않을까 싶다. 기본적으로 찌르기,내려치기,올려치기 등등 기술을 사용할때 정도?