개발이야기/C#

[C#]추상클래스(abstract), 인터페이스(interface)는 어떻게 다르게 쓰일까

Kim_Jack 2022. 10. 7. 00:05
반응형

추상클래스(abstract), 인터페이스(interface)와 그 차이점에 대해 얘기해보고

그러한 차이점이 생기도록 디자인되었나를 써보겠습니다.

차이점에 대해 알더라도 그러한 차이가 있는지를 생각해보고 알맞게 사용해야 정말 그걸 안다고 말할 수 있다고 생각합니다.

 

우선 OOP에서 의미하는 ‘추상’은 뭘까요 ?

어떠한 클래스를 정의할 때 공통된 기능을 추출하는걸 ‘추상화시킨다’라고 합니다.

 

예를 들어 게임에서 여러 종류의 몬스터가 존재한다고 가정해봅시다.

A몬스터는 근접에서 칼로 공격하고, B몬스터는 원거리에서 활로 공격하고, C몬스터는 마법 공격을 합니다.

 

이때 몬스터 클래스를 설계한다면 이 몬스터 클래스의 공격 기능을 추상화시켜야 합니다.

모든 몬스터는 공격이라는 공통된 기능이 있지만 그 공격의 종류는 전부 다릅니다.

그래서 공격 기능을 추상화시키고 이를 상속받아 각 타입에 맞는 공격으로 재정의 시켜 구현하는 게 가장 객체지향스러운 방법이겠죠.

 

추상클래스(abstract class)

그래서 우선 추상 클래스는 이렇게 생겼습니다.

public abstract class AbstractClass
{
    public int x; // 변수

    public abstract void FunctionA();

    public void FunctionB()
    {
        //일반함수
    }

    public virtual void FunctionC()
    {
        //가상함수
    }
}

추상 클래스 내부에는 일반함수, virtual함수, abstract함수, 변수 모두 다 선언할 수 있습니다.

abstract함수를 선언할 수 있다는 점을 제외하면 일반 클래스와 거의 유사합니다.

그러면 이 abstract함수가 가지는 의의는 뭘까요?

 

우선 추상함수를 살펴보면 선언만 되어있고 구현이 되어있지 않습니다.

abstract 키워드가 붙은 함수는 자식클래스에게 반드시 전달되어 재정의해야 하는 함수를 의미합니다.

위에 언급한 몬스터의 공격과 같은 거죠.

이 기능을 추상화 시켰으니 무조건 자식 클래스에서 정의해서 사용해! 이겁니다.

 public abstract void FunctionA();

 

그래서 이 추상 클래스를 상속받은 자식 클래스는 override하여 재정의합니다.

public class ChildClass : AbstractClass
{
    public override void FunctionA()
    {
        // 부모 추상클래스의 추상함수 구현
    }
}

 

그러면 virtual을 쓰면 되지 왜 abstract를 쓰는 거지?라는 의문이 들 수도 있습니다.

virtual함수는 내부에 구현이 가능합니다. 그리고 자식 클래스에서 재정의를 하지 않더라도 에러를 뿜지 않습니다.

이러한 부분에서 virtual함수는 추상화의 개념에서 약간 어긋납니다.

 

abstract는 자식 클래스에서 재정의를 하지 않으면 에러를 뿜습니다.

추상화시킨 기능을 자식 클래스에게 온전히 전달해 반드시 재정의해야 제대로 동작을 하도록 하는 거죠.

이러한 디자인된 부분에서 absract와 virtual은 그 사용용도가 엄연히 다릅니다.

 

인터페이스(Interface)

인터페이스는 기능들을 선언만 하고 구현이 되어있지 않습니다.

그리고 추상 함수와 똑같이 상속받은 클래스에 인터페이스의 선언돼있는 기능들을 정의시켜 사용해야 하는데요.

다른 점은 인터페이스는 변수나, 다른 일반 함수들은 선언할 수 없습니다. 오로지 함수의 선언만 가능합니다.

왜 이러한 기능을 만들었을까? 생각을 곰곰이 생각해보면 그 의도를 짐작해볼 수 있습니다.

 

우선 C#은 다중상속이 되지 않습니다.

그리고 인터페이스는 클래스로 취급하지 않기에 여러 인터페이스를 상속받아 구현할 수 있습니다.

그리고 일반클래스와 유사하게 사용할 수 있는 추상클래스와는 달리

인터페이스는 함수의 선언만 가능합니다, 이러한 디자인의 의도를 유추해보자면

 

추상클래스가 정의하는 클래스의 큰 뼈대를 담당하고, 인터페이스는 그 뼈대에 끼우는 파츠라고 볼 수 있습니다.

 

모든 클래스에서 추상화시키기에는 애매하지만, 여러 클래스에서 쓰고 있는 기능을 인터페이스로 묶어

그 기능이 필요한 클래스들에만 따로 상속하여 구현하여 사용할 수 도 있습니다.

 

예를 들면

몬스터 타입이 4개가 있습니다.

그리고 모든 몬스터는 전부 공격을 하지만 각자 다른 스타일의 공격을 합니다.

그러면 Monster를 추상 클래스로 만들고 공격을 추상화시켜 각 클래스에 상속시킵니다.

그런데 몬스터 타입 A,B는 제자리에서 공격을 합니다(move 기능이 필요가 없음)

하지만 몬스터 타입 C,D는 플레이어를 따라 움직이며 공격을 합니다 (move 기능이 필요)

이러한 상황에서 move 기능은 모든 클래스에게 적용되지 않기에 추상화시킬 수 없습니다.

이렇게 추상 클래스에서 전부 해결할 수 없을 때 move 기능을 인터페이스로 만들어 몬스터 C,D 클래스에게 상속해 구현할 수 있습니다.

 

 

클래스를 설계하다 보면 모든 클래스에게 추상화를 적용할 수 없는 상황이 종종 나타납니다.

그래서 다중 상속이 안 되는 C#에서는 interface를 통해 그러한 기능들을 묶어줍니다.

 

interface또한 자식클래스에서 무조건 정의해야 하는 순수 가상 함수이기에

다중상속보다 오히려 코드가 더 간결하고 복잡해지지 않는 점이 좋은거 같습니다.

 

추상클래스와 인터페이스를 제대로 이해하지 못하고 혼동하는 사람들이 꽤 많습니다.

하지만 이 둘은 이렇게 각각의 사용용도가 엄연히 다릅니다.

 

추상클래스는 큰 뼈대를 잡고 모든 클래스에 적용되는 기능을 추상화합니다.

인터페이스는 모든 클래스에서 추상화 시킬 수 없는 기능들에대하여 부분적으로 추상화가 필요할때 사용합니다.

 

그리고 제가 설명한 부분외에도 인터페이스의 활용 용도는 더 넓습니다.

인터페이스가 오로지 이러한 부분때문에만 존재한다고 이해하기보다는 

인터페이스가 크게 활용되는 용도 중 하나구나라고 이해하면 좋습니다. 

반응형