[유니티 / C#] 클래스의 상속(Inheritance)

⭐ 상속이란?

기존 클래스의 속성과 멤버를 물려받아 새로운 클래스를 만드는 것을 말합니다.

여기서 기존 클래스는 '부모 클래스', 새로 만든 클래스는 '자식 클래스' 라고 부릅니다.

 

상속을 받은 자식 클래스는 부모 클래스의 접근 제한자가public, protected인 멤버들을 물려 받습니다.

또한 자식 클래스에서 별도의 변수를 선언하고 메서드를 구현할 수 있습니다.

 


 

상속을 하는 이유

 

1. 코드 재사용: 중복되는 기능, 코드를 부모 클래스에서 상속받아 사용할 수 있기 때문에 효율적입니다.

 

2. 다형성: 상위 타입 참조로 다양한 하위 타입들을 처리할 수 있습니다. 또한 상위 타입으로 업캐스팅이 가능하기 때문에 자료구조 설계가 단순해집니다.

 

3. 계층적 설계와 확장성: 새로운 파생 클래스를 추가해도 기존 코드와 호환성이 좋습니다.

 

 

 

this 키워드와 base 키워드

 

💡 this 키워드

this 키워드는 현재 인스턴스, 즉

5. 자기를 참조할 때 사용하는 키워드입니다.

public class User
{
    private string name;
    
    public User(string name)
    {
        this.name = name;  //this.name -> User 클래스의 필드에 있는 name 변수 / name -> 매개변수 name
    }
}

 

 

💡 base 키워드

base 키워드는

5. 현재 클래스의 직접 상위 클래스 (부모 클래스)를 참조하는 키워드입니다.

public class A
{
    public void Print()
    {
        Console.WriteLine("부모 클래스");
    }
}

public class B : A -> B 클래스는 A 클래스를 상속받습니다.
{
    public void Print()
    {
        base.Print(); -> 부모 클래스의 Print() 함수 호출
        
        Console.WriteLine("자식 클래스");
    }
}
출력결과
-> 부모 클래스
   자식 클래스

 

상속의 상속일 경우 가장 아래 계층 클래스에서 base를 사용하면 바로 위의 부모 클래스를 지칭하게 됩니다.

public class A
{
    public void Print()
    {
        Console.WriteLine("부모 클래스");
    }
}

public class B : A
{
    public void Print()
    {
        Console.WriteLine("자식 클래스");
    }
}

public class C : B
{
    public void Print()
    {
        base.Print(); -> 바로 위 부모 클래스인 B 클래스를 지칭
        Console.WriteLine("짜식 클래스");
    }
}
출력 결과
-> 자식 클래스
   짜식 클래스

 

 

sealed 키워드

더 이상 상속될 수 없는 클래스를 선언할 때 sealed 키워드를 사용합니다.

특정 클래스가 더 이상 상속되지 않아야 함을 명시적으로 표현할 수 있으며, 의도치 않게 상속되는 상황을 사전에 방지할 수 있습니다.

 

//일반적인 상속 관계 => 사용 가능
public class Unit { }

public class Orc : Unit { }

public class InfectedOrc : Orc { }


//sealed 클래스가 껴 있는 상속 관계 => InfectedOrc는 Orc를 상속받지 못함
public class Unit { }

public sealed class Orc : Unit { }

public class InfectedOrc : Orc { }

상속을 이용한 예제

부모 클래스의 멤버를 자식 클래스에서도 사용할 수 있다는 것을 알았으니 어떻게 상속을 잘 사용할 수 있는지 예제를 통해서 알아보겠습니다.

 

아래 예제를 보시면 부모 클래스인 Game 클래스를 상속받고 있는 LostArk 클래스가 있습니다.

자식 클래스에서 따로 변수를 선언하지 않았지만 name과 id를 상속받았습니다.

하지만 private 한정자로 선언된 description의 경우 자식 클래스에 상속되지 않았습니다.

public class Game
{
    public string name;           //public 한정자 -> 상속 가능
    protected int id;             //protected 한정자 -> 상속 가능
    private string description;   //private 한정자 -> 상속 불가능
}

public class LostArk : Game
{
    public void Init()
    {
        name = "LostArk";
        id = 2982354;
        description = "레이드가 주류인 RPG 장르의 게임입니다.";  //선언되지 않은 변수로 Error 발생
    }
}

 

💡 protected 한정자

접근 제한자중의 한 종류인 protected 한정자는 외부에서는 접근이 불가능하지만 상속한 클래스에서는 접근이 가능한 특별한 지시자입니다.

 

 

다음은 상속에서의 생성자는 어떤 방식으로 호출되는지 알아보겠습니다.

 

상속에서의 생성자는 부모 클래스와 자식 클래스 모두 호출됩니다.

호출 순서는 부모 클래스 -> 자식 클래스 순서로 호출됩니다.

 

public class Game
{
    public string name;
    protected int id;

    public Game()
    {
        Console.WriteLine("부모 생성자"); -> 첫 번째로 호출
    }
}

public class LostArk : Game
{
    public void Init()
    {
        name = "LostArk";
        id = 2982354;
    }

    public LostArk()
    {
        Console.WriteLine("자식 생성자"); -> 두 번째로 호출
    }
}

출력 결과
-> 부모 생성자
   자식 생성자

 

: base()

부모 생성자와 자식 생성자의 매개변수 타입이나 개수가 다르거나, 부모 생성자가 여러개인 경우

5. 명시적으로 호출하고 싶은 생성자를 지정할 수 있습니다.

public class Game
{
    public string name;
    protected int id;

    public Game()
    {
        Console.WriteLine("부모 생성자");
    }

    public Game(string str)
    {
        Console.WriteLine(str);
    }
}

public class LostArk : Game
{
    public void Init()
    {
        name = "LostArk";
        id = 2982354;
    }

    public LostArk() : base("부모 생성자") -> : base(인자값) 을 통해 부모 생성자 호출
    {
        Console.WriteLine("자식 생성자");
    }
}

 


 

상속에서 배울 수 있는 다형성!

상속은 다형성을 구현하기 위한 수단 중 하나입니다.

다형성이란 하나의 타입을 통해 여러가지의 동작을 수행할 수 있는 것을 의미하는데 상속은 왜 다형성을 구현할 수 있는걸까요?

부모 클래스와 자식 클래스는 서로 업캐스팅, 다운캐스팅이 가능합니다.

단, 자식 클래스가 부모 클래스로 변환하는 업캐스팅은 대부분 문제없이 이루어지지만

반대인 다운캐스팅의 경우 꽤나 조건이 붙습니다.

 

 

✨ 업캐스팅

 

부모 클래스의 객체 변수에 자식 클래스 메모리 할당이 가능한 것을 의미합니다.

자동 변환이므로 캐스팅 연산자를 사용하지 않아도 됩니다.

부모 타입의 멤버만 접근 가능합니다.

 

//여기서 LostArk, MapleStory, Mobinogi 클래스는 모두 Game 클래스를 상속받고 있음
Game[] gameArr = { new LostArk(), new MapleStory(), new Mobinogi()};

 

업캐스팅의 장점

● 형식을 통일해 효율적으로 작업하기에 용이합니다. (예: 컬렉션에 여러 자식 객체를 저장할 때)

코드가 간결해지고 수정에 용이합니다.

 

 

 

 

✨ 다운캐스팅

 

● 부모 타입으로 참조 객체를 원래 자식 타입으로 다시 변환하는 것을 의미합니다.

●  명시적으로 캐스팅해야 합니다.

●  실제 객체가 자식일때만 성공, 부모 객체 자체를 시도하면 예외 처리가 발생합니다.

●  캐스팅을 안전하게 처리하기 위해 is, as 연산자를 사용합니다.

 

//Game = 부모 클래스, LostArk = 자식 클래스

LostArk lostArk = new LostArk();
Game game = (Game)lostArk; -> (Game)으로 명시적 캐스팅

 


 

is, as 연산자

 

💡 is 연산자

 

● 객체가 해당 타입과 호환되는지 검사 후 bool 값으로 반환합니다. ( 해당 타입이거나 해당 타입으로부터 파생된 타입이면 true)

● 값, 참조 형식 모두 사용 가능합니다.

LostArk lostArk = new LostArk();
Console.WriteLine(lostArk is Game); -> lostArk 객체가 Game 타입이거나 파생된 타입일 경우

출력 결과
-> True

 

 

💡 as 연산자

 

● 객체를 지정한 타입으로 캐스팅 시도, 실패하면 null을 반환합니다.

● 참조 형식에만 사용 가능합니다. * nullable 값 형식(int?)은 가능

LostArk lostArk = new LostArk();
Game game = lostArk as Game;

 


⭐ 오버라이딩

부모 클래스에서 정의한 메서드를 자식 클래스에서 재정의하는 것을 의미합니다.

 

● 부모 클래서에서 virtual로 정의된 메서드를 자식 클래스에서 override 키워드를 통해 재정의가 가능합니다.

● 부모 메서드는 public, proteced 한정자로 정의해야 합니다.

● 메서드의 이름, 매개변수 타입, 개수, 반환타입이 동일해야 합니다.

● 부모 메서드와 같은 접근 제한자를 사용해야 합니다.

public class Game
{
    protected virtual void Test()
    {
        Console.WriteLine("부모 메서드 오버라이딩");
    }
}

public class LostArk : Game
{
    protected override void Test()
    {
        base.Test();
        Console.WriteLine("자식 메서드 오버라이딩");
    }
}

출력 결과
-> 부모 메서드 오버라이딩
   자식 메서드 오버라이딩