공부/인프런 - Rookiss

Part 1-7-2. 기타 문법 : Interface(인터페이스) + 다중 상속

셩잇님 2023. 8. 3. 16:16
반응형

 

 

추상 클래스(abstract)

 

using System;
using System.Collections.Generic;

namespace CSharp
{
    abstract class Monster
    {
        public abstract void Shout(); 
    }

    class Orc : Monster
    {
        public override void Shout() { Console.WriteLine("오크~~"); }
    }

    class Skeleton : Monster
    {
        public override void Shout() { Console.WriteLine("빠각~~"); }
    }

    class SkeletonOrc : Orc, Skeleton // ❌❌ 컴파일 에러 ! 
    {

    }

    class Program
    {
        static void Main(string[] args)
        {
            Monster monster = new Monster(); // ❌❌ 컴파일 에러! 추상 클래스 타입의 객체는 만들 수 없다.

            Monster monster = new Orc(); // ⭕ 다형성
        }
    }
}
 

 

추상 클래스의 정의

 

    abstract class Monster
    {
        public abstract void Shout(); 
    }

 

[추상 클래스]

추상 클래스는 abstract 추상 함수를 가지고 있는 클래스를 뜻한다.

  • 추상 함수에도 abstract가 붙고, 추상 함수를 가지고 있다면 클래스 정의 앞에도 abstract를 붙여주어야 한다.
  • 추상 함수 말고도 가상 함수나 일반 멤버 함수나 멤버 변수를 가질 수 있다.
  • 어느 정도 구체적인 부분은 가질 수 있다는 얘기다. 👉 자식 클래스에게 물려주기 위해서

 

[추상 함수]

  • 자식 클래스에서 반드시 오버라이딩 하도록 강제하는 함수다.
  • 가상 함수처럼 업 캐스팅시 자식이 오버라이딩한 함수를 호출하도록 한다. 👉 다형성
  • 가상 함수와는 달리 구현부{}를 작성하면 안된다. 함수의 인터페이스만 정의한다. 그래서 추상적으로 존재만 하는 함수라 추상 함수다!
  • 오로지 구현은 자식클래스에게 맡기며 이러이러한 기능을 꼭! 꼭! 재정의 해달라는 신호와도 같다.
  • 자식클래스에서 이를 오버라이딩 하지 않으면 컴파일 에러가 발생한다.

 

[추상 클래스 주의할 점]

 

1️⃣ 추상 클래스 타입의 객체 생성은 불가능하다.

 

Monster monster = new Monster(); // ❌❌ 컴파일 에러! 추상 클래스 타입의 객체는 만들 수 없다.
Monster monster = new Orc();  // ⭕ 다형성

 

추상클래스인 Monster타입의 객체를 생성할 수는 없다. 👉 컴파일 에러

  • 추상 클래스엔 구현부가 없는 추상 함수가 있기 때문이다.
  • 추상 클래스가 존재하는 이유는 그 자체로 객체를 만들기 위함이 아니라, 자식 클래스에게 필요한 것들을 상속해주고, 특정 추상 함수를 꼭 구현하라고 강제하기 위함이다.

 

다만 추상클래스 타입의 변수가 자식 클래스를 참조하는 업캐스팅은 가능하다. 👉 다형성

 

 

2️⃣ 다중 상속이 불가능하다 + 그 이유


C#에서는 추상 클래스와 그 외 평범한 일반 클래스들은 다중 상속이 불가능하다. 그 이유는 죽음의 다이아몬드 문제가 생길 수 있기 때문이다!

 

    abstract class Monster
    {
        public abstract void Shout(); 
    }

    class Orc : Monster
    {
        public override void Shout() { Console.WriteLine("오크~~"); }
    }

    class Skeleton : Monster
    {
        public override void Shout() { Console.WriteLine("물캉~~"); }
    }

    class SkeletonOrc : Orc, Skeleton  // ❌❌❌ 컴파일 에러 발생!
    {

    }
    
- 📜Monster
	- 📜Orc
	- 📜Skeleton

 

Orc 클래스와 Skeleton 클래스는 Monster 추상 클래스를 상속받는다.

  • 따라서 Orc 클래스와 Skeleton 클래스에서는 반드시 추상 함수 Shout()를 오버라이딩 해야 한다.

 

SkeletonOrc 클래스에서 Orc,Skeleton 를 둘 다 상속 받으려고 한다면 (다중 상속을 받는다고 가정해본다면)

  • SkeletonOrc 클래스에게 주어진 선택지는 2가지이다.
  • 1️⃣ Shout()를 새롭게 오버라이딩 한다. 👉 문제 없다. 자신만의 내용으로서 새롭게 정의하는 것이기 때문에.
  • 2️⃣ 부모인 Orc,Skeleton 로부터 물려받은 Shout()를 오버라이딩하지 않고 물려받은 그대로 사용한다. 👉 이 경우 문제 발생 !!
  • 부모인 Orc,Skeleton 둘 다 각자 오버라이딩 해서 구현부가 다른기 때문에 두 부모 클래스의 Shout()은 서로 다른 함수나 마찬가지다. 따라서 SkeletonOrc 입장에선 어떤 Shout()을 상속 받아야 하는지 알 수 없다.
  • 죽음의 다이아몬드 문제 👉 그대로 상속 받아 쓰려고 할 때, 두 부모가 동일한 함수이나 오버라이딩으로 구현부가 다른 함수를 가지고 있기 때문에 오버라이딩 하지 않고 그대로 물려 받아 사용하려고 할 때 어떤 것을 사용해야할지 애매해지는 문제

 


 

인터페이스

 

using System;
using System.Collections.Generic;

namespace CSharp
{
    interface IFlyable
    {
        void Fly();  // 접근 지정자 안붙임. abstract 이런것도 안붙임
    }

    class FlyableOrc : Orc, IFlyable
    {
        public void Fly()
        {

        }
    }

    class Program
    {
        static void DoFly(IFlyable flyable)
        {
            flyable.Fly(); // 아무 문제 없다.
        }
        static void Main(string[] args)
        {
            IFlyable flyable = new FlyableOrc();  // 다형성
        }
    }
}

 

인터페이스의 정의

추상함수’만’ 가지고 있는 클래스. 추상 클래스와 달리 다중 상속이 가능하다.

  • 앞에 interface 키워드를 붙여주며, class는 붙이지 않는다.
  • 추상 함수는 접근 한정자 같은 어떠한 키워드도 붙지 않는다. 기본적인 함수 프로토타입만 정의한다.
  • 인터페이스 이름 앞엔 I를 붙여주는게 암묵적 약속이다.
  • 멤버 변수나 가상 함수나 다른 멤버 함수를 가질 수 없으며 오로지 추상 함수만 가진다. 즉 정말 인터페이스의 역할만 하는 것이다.

 

자식 클래스에게 이러 이러한 기능들을 꼭 너네들 방식으로 꼭꼭 구현해라! 라는 인터페이스적인 의미만 가지고 있다. 따라서 자식 클래스들에게 자신만의 구체적인 데이터를 그대로 물려주는건 단 하나도 없다. 오로지 추상함수들만 가지기 때문이다.

 

결론. 그래서 인터페이스를 다중 상속할 수 있다. 그대로 물려주는게 하나도 없기 때문에 자식들은 무조건 오버라이딩을 해야 한다. 따라서 인터페이스를 다중 상속 받을 땐 죽음의 다이아몬드 문제가 발생하지 않는다. (선택지를 1️⃣로만 강제함)

 

class FlyableOrc : Orc, IFlyable  // 👉 인터페이스를 이렇게 다중 상속하는 것이 가능 ! 
{
    public void Fly()
    {
                
    }
}

 

추상 클래스와 마찬가지로 인터페이스 타입의 객체는 생성할 수 없지만 인터페이스 타입의 변수를 선언하여 자식 타입의 객체를 업캐스팅 할 수는 있다. 👉 다형성

 

    static void DoFly(IFlyable flyable)
    {
        flyable.Fly(); // 아무 문제 없다.
    }
    static void Main(string[] args)
    {
        IFlyable flyable = new IFlyable(); // ❌❌ 컴파일 에러
        IFlyable flyable = new FlyableOrc();  // ⭕ 다형성
    }

 

 위 flyable.Fly(); 이 코드 한줄은, flyable이 어떤 자식 타입인지에 따라 각각 다르게 실행될 것이다. 모든 자식들은 자신만의 Fly()로 재정의를 해야하기 때문이다.

 

 

 

 
반응형