멈추지 않고 끈질기게

[C#] 추상 클래스(abstract class) 본문

카테고리 없음

[C#] 추상 클래스(abstract class)

sam0308 2023. 3. 27. 15:13

※ 해당 포스팅은 개인의 공부 정리용 글입니다. 틀린 내용이 있다면 추후 수정될 수 있습니다.

※ 해당 포스팅은 .Net 5.0 버전을 기준으로 작성되었습니다.

 

 

 

 

이번 포스팅에서는 추상 클래스에 대해 알아보겠습니다.

1. 추상 클래스(abstract class)의 정의

 추상 함수(abstract function)는 함수의 내용을 구현하지 않는 함수로, 함수의 반환형 앞에 abstract 키워드를 붙여 선언하고 구현부를 생략하고 세미콜론(;)으로 마무리합니다. 이러한 추상 함수를 1개이상 포함하는 클래스추상 클래스(abstract class)라고 합니다. 반대로 말하자면, 클래스 안에 추상 함수를 선언했다면 해당 클래스는 반드시 추상 클래스로 만들어야 합니다. 추상 함수 외에 일반적인 멤버 변수나 함수도 포함할 수 있습니다.

abstract class WeaponBase // 추상 클래스
    {
        protected int id; // 멤버 변수

        public abstract void Initialize(); // 추상 함수

        public void ChangeId(int id) // 일반 멤버 함수
        {
            this.id = id;
        }
    }

 

 추상 클래스는 함수의 구현부가 정의되지 않은 추상 함수를 포함하므로 인스턴스화 할 수 없습니다. 추상 클래스의 인스턴스를 선언할 경우 컴파일 에러를 발생시킵니다.  대신 추상 클래스 타입의 변수로 자식 타입의 인스턴스를 가리키는 것은 가능합니다.

 

사진 1. 추상 클래스의 인스턴스 선언

 

 

2. 추상 클래스를 사용하는 이유

 추상 클래스를 상속받을 경우, 그 안에 포함된 모든 추상 함수를 반드시 오버라이딩해야 합니다. 이를 어길 경우 컴파일 에러가 발생하고 빌드가 실패하게 됩니다.

 

사진 2. 추상 함수 미구현

 따라서 상속 받는 자식 클래스에서 반드시 개별 구현해야하는 내용이 있다면, 해당 내용을 추상 함수로 선언하여 누락하는 일이 없도록 할 수 있습니다(이 경우 부모 클래스는 추상 클래스로 선언해야 합니다). 가상 함수(virtual function)가 특정 자식 클래스에서만 별도의 내용으로 구현해야 하는 내용을 다루는 용도라면, 추상 함수는 자식 클래스마다 다르게 구현해야 하는 내용을 정의해 두는 용도로 적합합니다. 

 

 

 

3. 추상 클래스 vs 인터페이스

 사실 추상 클래스와 비슷한 역할을 하는 인터페이스가 존재합니다. 인터페이스 또한 추상 함수를 포함하며 단독으로 인스턴스화 할 수 없다는 점에서 추상 클래스와 비슷하나, 차이점은 인터페이스는 일반 멤버 변수나 함수를 선언할 수 없습니다.

 둘 다 추상 함수를 포함하는 만큼, 상속받은 클래스에서 반드시 구현해야 하는 점은 비슷합니다. 다만 인터페이스의 경우 추상 함수 외에 일반 멤버 함수나 변수를 선언할 수 없는 대신, C#은 원래 다중 상속을 지원하지 않지만 인터페이스는 갯수 제한 없이 여러개를 동시에 상속받을 수 있습니다.

interface IPrintInfo
{
    public abstract void PrintInfo(); // 추상 함수
}

abstract class WeaponBase
{
    protected int weaponId;
    protected int damage;

    public abstract void Initialize(); // 추상 함수

    public void ChangeId(int id) // 일반 멤버 함수
    {
        weaponId = id;
    }
}

class Sword : WeaponBase, IPrintInfo
{
    public Sword()
    {
        weaponId = 1001;
        damage = 20;
    }

    public override void Initialize() // 가상 함수 오버라이딩
    {
        Console.WriteLine("Sword 초기화");
    }

    public void PrintInfo() // 인터페이스 구현
    {
        Console.WriteLine($"해당 무기의 id: {weaponId}");
    }
}

class Spear : WeaponBase, IPrintInfo
{
    public Spear()
    {
        weaponId = 2001;
        damage = 10;
    }

    public override void Initialize() // 가상 함수 오버라이딩
    {
        Console.WriteLine("Spear 초기화");
    }

    public void PrintInfo() // 인터페이스 구현
    {
        Console.WriteLine($"해당 무기의 id: {weaponId}");
    }
}

class Program
{
    static void Main(string[] args)
    {
        // 추상 클래스 변수를 자식 클래스 객체로 초기화
        WeaponBase wb1 = new Sword();
        wb1.Initialize(); // Sword의 Initialize() 실행

        WeaponBase wb2 = new Spear();
        wb2.Initialize(); // Spear의 Initialize() 실행

        IPrintInfo iw1 = wb1 as IPrintInfo;
        iw1.PrintInfo();
    }
}

사진 3. Initialize() 및 PrintInfo() 실행 결과

 상기 코드에서는 IPrintInfo 인터페이스와 WeaponBase 추상 클래스를 정의한 뒤 Sword, Spear 클래스에서 각각 상속받도록 하였습니다. 추상 클래스인 WeaponBase 의 경우 무기에 공통으로 들어갈 id와 공격력과 같은 정보를 포함하며, Initialize() 함수를 추상 함수로 선언하여 상속받은 클래스에서 개별 구현하도록 했습니다. 메인 함수를 보면 WeaponBase 타입의 변수로 Sword, Spear 객체를 가리키게 하고 Initialize() 함수를 실행하였으며, 각각 Sword와 Spear 클래스에서 오버라이딩한 함수로 실행되는 것을 알 수 있습니다. 이름에서 알 수 있듯이 무기 클래스들의 기본 정보 및 기능을 담당하는 용도로 사용하고 있습니다.

 

 IPrintInfo 인터페이스의 경우 객체의 정보를 출력하는 PrintInfo() 함수를 정의하게 하였으며, 해당 함수에서 어떤 정보를 출력할지는 상속받은 클래스에서 정하기 나름입니다. 해당 인터페이스에는 객체 정보 출력용 PrintInfo() 함수 외에 다른 기능은 없으며 이 기능은 객체 타입에 종속적이지 않으므로, 꼭 무기 관련 클래스 뿐 아니라 몬스터, 소모아이템, 재료아이템 등 다양한 클래스에서 상속받아 사용하도록 할 수 있습니다.