
이전에 올린 글들에서 계속 상속이 나왔다.
그만큼 상속은 객체지향적인 C#에게 필수적이다.
오늘은 상속을 알아보도록 하자 늘 그렇듯 공식 메뉴얼은 훨씬 디테일하다.
필자가 글 쓰는데도 2일걸렸기에 이해안되면 천천히 다시 읽어보길
#1 상속(inheritance)이란?
우리가 흔히 아는 그 상속 비슷한데 부모의 재산을 상속한다 할 때 그 상속이랑 비슷하게
C#에서는 자식이 부모의 기능을 물려받는거다.
객체지향에선 부모 클래스와 자식 클래스로 나뉘는데 아래와 같이 불린다.
부모 클래스 = 베이스 클래스 = 슈퍼 클래스
Parent Class = Base Class = Super Class
자식 클래스 = 파생 클래스 = 서브 클래스
Child Class = Derived Class = Sub class
어떤식으로 부르던간에 클래스의 상속관계를 따로따로 구분만 할 수 있으면된다.
부모 자식 클래스의 관계는 다음과 같다.
"~이다 관계" 영어로 Is-A Relationship
한 클래스가 다른 클래스의 파생(자식) 클래스라는 아주 긴밀한 관계라는 걸 뜻한다.
따라서 부모(베이스) 클래스가 손상이 일어나면 자식(파생) 클래스도 손상 될 위험이 크다는 뜻이다.
반댓말인 has-a 관계나 비슷한 개념인 can do 관계도 있다.
궁금하면 아래 링크의 클을 보도록하자 ↓
유니티 C#에서 is-a관계? has-a 관계? 그리고 can-do관계?
자식이 부모의 기능을 물려받는댔는데

자식 클래스 객체 생성하면 사진처럼 부모 클래스까지 포함되어 생성되기에
부모 클래스의 모든 기능을 쓸 수 있게된다. 이러한 특징은 다형성이라고도 한다.
#2 C#의 상속 특징
그럼 C#에서 상속은 어떤 특징이 있을까?
부모 클래스의 멤버들중 아래처럼 자식 클래스로 상속이 안되는게 있다.
- 정적 생성자 (static 키워드)
- 인스턴스 생성자 (new로 객체 생성)(인스턴스 멤버 변수 생성 및 초기화)
- 종료자 (~ 키워드)
이외엔 거의 모든 멤버가 상속된다.
객체를 생성하면 부모의 생성자가 자동으로 먼저 호출되고 소멸(종료)될 땐 자식에서 부모 순서대로 호출된다.
┌코드로 좀 더 보기 (모르는 키워드는 밑에 #3에서 설명)
class 부모
{
protected 부모(int 부모매개변수) //* 부모 클래스의 생성자 호출
{
}
}
class 자식 : 부모
{
//* 이러면 부모 생성자 먼저 호출
public 자식(int 자식매개변수) : base(자식매개변수)
{
}
}
그러니까 자식(int 자식매개변수) 생성자 실행하면 base인 부모(int 부모매개변수) 생성자 먼저 호출이 된다.
그 다음 자식 생성자 순서다. base()부분 떼버려도 똑같은 결과가 나온다.
┌base 키워드는 무슨 역할?

사진만 보면 이해가 될 것이다.
base는 상속한 부모 클래스를 가리킨다.
부모 메서드랑 자식 메서드 이름 같아버리면
구분하고 싶으니까 base만 쓰면 구분이 된다.
base 키워드 또한 직접 에디터에서 마우스 갖다대면 자세히 알려준다.
#3 클래스 상속(inheritance)하는법
가장 기본적은 상속은 클래스 상속이다.
클래스 상속은 클래스 이름 뒤에 콜론 ' : ' 써주고 그 뒤에 상속할 클래스 이름 쓰면된다.
class 자식클래스 : 부모클래스
{
//* 이런느낌?
}
이렇게 쓰면 위에 소개한거처럼
자식클래스는 부모클래스의 모든 멤버를 물려받아 다 접근가능하다.
(만약 부모클래스에서 private 쓰면 당연히 접근 안됨)
┌아래는 유니티로 상속 써본 예시 ↓
class 부모 : MonoBehaviour
{
protected 부모() //* 부모 클래스의 생성자 호출
{
}
protected int 부모의변수;
protected void Start() {
부모의변수 = 1;
}
}
class 자식 : 부모
{
public 자식(int 매개변수) //* 자식 클래스의 생성자 호출하면서 매개변수 변경
{
this.부모의변수 = 매개변수; //* 부모의 멤버를 쓸 수 있는 모습!
}
private new void Start() {
base.Start();
}
}
위에 코드에서 눈여겨봐야할껀
1. 자식이 부모 상속하니까 부모가 상속한 MonoBehaviour 기능 또한 상속됨
2. 부모클래스의 protected 키워드 붙인 멤버들은 상속한 자식들만 접근 가능하다.
3. 부모클래스 멤버는 접근제한자 따로 안쓰면 기본 private이기에 protected나 public을 붙여주자
4. 부모의 Start()와 자식 Start()는 이름 같기에 base. 키워드로 부모의 Start()를 가리킬 수 있다.
5. new 지정자로 부모 클래스의 Start()함수를 숨길 수 있다.
6. 부모 Start()를 숨겼으니 자식은 자신만의 Start() 메서드를 쓰게 된다.
아래 사진은 위 코드의 Start() 실행 순서 디버깅함 ↓

#4 추상 클래스(Abstract Class) 키워드 "abstract"
상속하면 추상 클래스도 언급하는데 추상 클래스 또한 is-a 관계다.
표현하기 힘든 중요한 부분이나 특징을 추상적으로 딱 골라내서 그 부분을 공통적으로 묶는거다.
추상클래스와 인터페이스는 비슷하지만 사용 목적이 다르므로 구분해야한다.
┌추상 클래스 VS 인터페이스
추상 클래스는 상속을 받아 기능을 이용하거나 확장을 하기 위함이고
인터페이스는 구현의 강제에 목적을 두어 구현 객체의 같은 동작을 보장한다는 데에 있다.
뭣보다도 둘의 가장 큰 차이는
인터페이스의 장점인 다중 상속 유무다
조금 더 깊게 파고 들어가면
추상 클래스는 상속의 개념이라
사용의도가 is-a 관계에 있지만
인터페이스는 의도가 다르게
can-do 관계에 있다.
이에 대한 자세한 설명은 아래에 ↓
추상클래스는 객체 생성이 안된다는 특징이 있다. 즉, new 연산자로 클래스의 객체 생성이 안된다.
또한 추상 클래스안에는 추상 메서드(함수)와 추상 프로퍼티(get,set)만 올 수 있다.
인터페이스랑 비슷하게 자식클래스에서 상속해야만 구현가능하다.
C#에서는 아무 클래스 명 앞에 abstract 키워드를 붙이면 추상클래스가 된다.
추상클래스의 안에 멤버에도 abstract 붙일 수 있는데
이는 자식 클래스에서 구현해야한다는 뜻이다.
위의 조건들을 토대로 추상클래스 ↓
abstract class 추상클래스 {
추상클래스() //* 생성자 메서드
{
}
public abstract void 추상메서드(); //* 자식에서 구현
public abstract int 추상프로퍼티{get;set;} //* 자식에서 구현
}
코드를 보면 모두 public 붙여주었다.
그 이유는 추상 클래스 안의 멤버들은 접근제한자 안붙이면 기본적으로 private이기에 에러 뜬다.
자식클래스에서 상속된 abstract 메서드(함수)들을 구현할려면 새로 재정의를 해서 override 키워드를 쓴다.
override에 대해서는 밑에 #6에 계속 설명
#5 가상 함수(Virtual Function) 키워드 "virtual"
추상 클래스 다음은 가상 함수차례인데, 왜 가상 클래스가 아니라 가상 함수일까?
찾아보니 추상화면 추상화답게 모호해서나중에 구현해야하는데
가상 함수는 기능이 이미 완성되있는 함수를 상속하기 때문이기도하고 다중 상속 안되서 필요없단 말도 있다.
가상 함수 또한 추상클래스 비슷하게 가상 메서드(함수)와 가상 프로퍼티(get,set)만 올 수 있다.
사용법은 아주 간단하다
protected virtual void 부모메서드()
{
//* 부모의 메서드(함수)
}
부모 클래스 안에 있는 메서드(함수)를 골라서 접근제한자 뒤에 "virtual" 써주면 된다.
이러면 자식 클래스에서는 재정의가 가능해진다.
protected override void 부모메서드()
{
//* 자식클래스 안에서 부모메서드 재정의하기
}
재정의할 땐 접근제한자 뒤에 "override" 써주면 된다.
주의! 부모랑 똑같은 접근제한자(protected) 써야 에러 안뜸
┌자식 클래스에서 public 써서 에러뜸 ↓

추상 클래스는 공통적으로 묶고 디테일한건 무조건 재정의해서 확장할려는게 목적이고
가상 함수는 처음부터 기능을 하는 완전한 함수다.
주로 자식 클래스에서 상속을 하면 기능을 추가하거나 필요할 때 override 키워드로 재정의해서 사용을 한다.
override에 대해서는 밑에 #6 계속 설명
#6 오버라이딩(Overriding) 키워드 "override"
오버라이딩 뜻은 말그대로 덮어쓴다는 말이다.
우리가 배우고 있는 상속에선 부모 클래스의 변수나 메서드(함수)를 자식 클래스에서 재정의해서 덮어쓰는 것이다.
때문에 동일한 메서드(함수) 호출에도 각 객체마다 다른 행동이 가능하게 된다.
override 키워드는 위에서 배운 virtual이나 abstract과 함께 쓴다.
┌추상 클래스 abstract + 재정의 override
abstract class 부모
{
protected abstract void 부모메서드();//* 부모의 메서드
}
class 자식 : 부모
{
protected override void 부모메서드()
{
//* override로 상속한 메서드 구현 가능
}
}
┌가상 함수 virtual + 재정의 override
abstract class 부모
{
protected virtual void 부모메서드()
{
//* 부모의 메서드(함수)
}
}
class 자식 : 부모
{
protected override void 부모메서드()
{
//* 자식클래스 안에서 부모메서드 재정의하기
}
}
┌위에 #3에서 new 쓰던데 그건 뭐지?
부모 클래스의 단순한 일반 함수를
재정의할 때는 new지정자를 써서 숨기면되고
override는 부모 클래스에서 재정의를 허락한 경우
(abstract & virtual)에 사용하는 것이다.
override 기능은 위에 처럼만 쓰면되고 더 많은 override 활용법도 있지만 어렵다.
#7 봉인 씰 키워드 "sealed" 한정자
이것도 상속에서 쓰는 건데 이름 그대로 클래스를 봉인하는거다.
클래스 명 앞에 sealed 적어 주면 다른 클래스가 상속하지 못하게 막는다.
┌예시 ↓
sealed class 부모
{
void 부모메서드()
{
//* 부모의 메서드(함수)
}
}
실제로 저렇게 했는데 상속할려고 하면 에디터에서 해결책으로 요렇게 표시한다.

번역이 이상하지만 봉인은 언제든지 해제 가능하다.
지금까지 소개한거 외에 as 연산자 is 연산자도 있다. 이 부분은 따로 분리해야겠다.
상속은 분명 강력한 기능이지만 초보자가 홀려서 상속을 막 쓰면 나중에 후회한다.
유명한 밈이있는데
바나나 클래스가 필요할 뿐인데 상속관계를 넓히다 보면 복잡해져서
바나나가 상속하는 원숭이가 상속하는 나무가 상속하는 정글로 이어지게 된다.
상속은 함부로 쓰면 위험하니까 내가 지금 해결하는 문제에 꼭 필요하다면 쓰도록하자!
'유니티 > 유니티 관련 지식' 카테고리의 다른 글
유니티 체력바 구현하는 법 (0) | 2023.02.23 |
---|---|
유니티 C#에서 is-a관계? has-a 관계? 그리고 can-do관계? (0) | 2023.02.18 |
유니티 프리팹 배리언트(Prefab variant)? 프리펩 상속 느낌? (0) | 2023.02.17 |
유니티 인터페이스(Interface) 실전 사용해보기 (0) | 2023.02.15 |