[C#] 14. 인터페이스(Interface)


Study/C#  2020. 7. 15. 18:07

안녕하세요. 명월입니다.


이 글은 C#에서의 인터페이스(Interface)에 대한 글입니다.


이전 글에서 인스턴스 생성하는 방법과 상속에 대해서 설명했습니다.

링크 - [C#] 10. 인스턴스 생성(new)과 메모리 할당(Stack 메모리와 Heap 메모리) 그리고 null

링크 - [C#] 12. 클래스 상속과 재정의(override)하는 방법과 override와 new의 차이


클래스의 공통적인 내용을 추상 클래스로 만들어서 상속받고 클래스를 정의할 수 있습니다.

그런데 이 공통적인 내용을 정의하는 것이 하나의 클래스가 아닌 두개의 클래스에서 상속을 받아야 한다고 생각해 봅시다.

using System;
namespace Example
{
  // ATypeAbstractClass 추상 클래스
  abstract class ATypeAbstractClass
  {
    // 추상 메서드
    public abstract string GetATypeName();
  }
  // BTypeAbstractClass 추상 클래스
  abstract class BTypeAbstractClass
  {
    // 추상 메서드
    public abstract string GetBTypeName();
  }
  // 위 두개의 추상 클래스를 상속받는다.
  class Example : ATypeAbstractClass, BTypeAbstractClass
  {
    // ATypeAbstractClass의 추상 클래스 함수 재정의
    public override string GetATypeName()
    {
      return "A";
    }
    // BTypeAbstractClass의 추상 클래스 함수 재정의
    public override string GetBTypeName()
    {
      return "B";
    }
    // 출력 함수
    public void Print()
    {
      // 콘솔 출력
      Console.WriteLine("GetATypeName - " + GetATypeName());
      Console.WriteLine("GetBTypeName - " + GetBTypeName());
    }
  }
  class Program
  {
    // 실행 함수
    public static void Main(string[] args)
    {
      // 인스턴스 생성
      Example ex = new Example();
      // 출력 함수
      ex.Print();
      // 아무 키나 누르시면 종료합니다.
      Console.WriteLine("Press any key...");
      Console.ReadLine();
    }
  }
}

에러가 발생합니다. 이유인 즉, 다중 상속 에러입니다.

사실 위처럼 사용한다면 다중 상속의 에러가 발생하지 않습니다. 그러나, Print함수가 Example에 있는 것이 아니고 추상 클래스에 있다면... Print 함수를 호출할 때, ATypeAbstractClass의 클래스 Print함수를 사용할 지, BTypeAbstractClass의 클래스의 Print함수를 사용할 지 모호해 집니다.

그래서 C#에서는 이처럼 다중 상속을 금지하고 있습니다.


이건 Heap의 메모리에 할당할 때의 이야기입니다.


그럼 Stack 메모리의 변수 선언할 때는 객체의 특성상 객체 별로 정리 정돈(?)이 필요할 때가 있습니다.

using System;
namespace Example
{
  // IATypeInterface 인터 페이스
  interface IATypeInterface
  {
    // 메서드 정의
    string GetATypeName();
    void Print();
  }
  // IBTypeInterface 인터 페이스
  interface IBTypeInterface
  {
    // 메서드 정의
    string GetBTypeName();
    void Print();
  }
  // 위 두 개의 인터페이스를 상속받는다.
  class Example : IATypeInterface, IBTypeInterface
  {
    // IATypeInterface의 인터페이스 함수 정의
    public string GetATypeName()
    {
      return "A Example";
    }
    // IBTypeInterface의 인터페이스 함수 정의
    public string GetBTypeName()
    {
      return "B Example";
    }
    // 출력 함수
    public void Print()
    {
      // 콘솔 출력
      Console.WriteLine("Example - GetATypeName - " + GetATypeName());
      Console.WriteLine("Example - GetBTypeName - " + GetBTypeName());
    }
  }
  // IATypeInterface 인터페이스를 상속받는다.
  class AExample : IATypeInterface
  {
    // IATypeInterface의 인터페이스 함수 정의
    public string GetATypeName()
    {
      return "A - AExample";
    }
    // 출력 함수
    public void Print()
    {
      // 콘솔 출력
      Console.WriteLine("AExample - GetATypeName - " + GetATypeName());
    }
  }
  // IBTypeInterface 인터페이스를 상속받는다.
  class BExample : IBTypeInterface
  {
    // IATypeInterface의 인터페이스 함수 정의
    public string GetBTypeName()
    {
      return "B - BExample";
    }
    // 출력 함수
    public void Print()
    {
      // 콘솔 출력
      Console.WriteLine("BExample - GetBTypeName - " + GetBTypeName());
    }
  }
  class Program
  {
    // 실행 함수
    public static void Main(string[] args)
    {
      // 인스턴스 생성
      Example ex = new Example();
      // 출력 함수
      ex.Print();
      // 인스턴스 생성
      AExample ex1 = new AExample();
      // 출력 함수
      ex1.Print();
      BExample ex2 = new BExample();
      // 출력 함수
      ex2.Print();
      // 아무 키나 누르시면 종료합니다.
      Console.WriteLine("Press any key...");
      Console.ReadLine();
    }
  }
}

Example 클래스를 보시면 IATypeInterface 인터페이스와 IBTypeInterface 인터페이스를 상속 받았습니다.

즉, IATypeInterface의 함수와 IBTypeInterface의 함수를 정의해야 합니다.

그래서 두 인터페이스를 상속 받은 Example 클래스는 두 인터페이스의 함수를 구현합니다.

AExample 클래스의 경우는 IATypeInterface 인터페이스를 상속 받았기 때문에 IATypeInterface 인터페이스의 함수를 구현합니다.

BExample 클래스의 경우는 IBTypeInterface 인터페이스를 상속 받았기 때문에 IBTypeInterface 인터페이스의 함수를 구현합니다.

그리고 Main함수에서 인스턴스를 생성하여 Print 함수를 호출합니다.


인터페이스는 추상 클래스와 다르게 안에 맴버 변수나 일반 함수를 구현할 수 없습니다. 단지 정의만 하는 것입니다. 그렇기 때문에 상속 받는 함수는 재정의(override)를 표현하는 것이 아닙니다.

이렇게만 보면 내부의 동작 구현이나 메모리의 할당의 맴버 변수를 구현하는 것도 아니고 단순히 정의만 하는 것인데 이게 왜 필요 있을까 싶습니다.


그러나 Stack 메모리에 변수의 포인터를 넣고 Heap 메모리에 클래스를 할당한다. 즉, 실체는 Heap 메모리에 있는 것이고 그것을 우리는 객체(Object)라고 합니다.

간단한 프로그램에서는 이 객체가 몇 안되겠지만 큰 프로그램에서는 이 객체가 많습니다. 우리는 이 객체를 리스트나 배열로 정리할 필요가 있습니다.

using System;
namespace Example
{
  // IATypeInterface 인터 페이스
  interface IATypeInterface
  {
    // 메서드 정의
    string GetATypeName();
    void Print();
  }
  // IBTypeInterface 인터 페이스
  interface IBTypeInterface
  {
    // 메서드 정의
    string GetBTypeName();
    void Print();
  }
  // 위 두 개의 인터페이스를 상속받는다.
  class Example : IATypeInterface, IBTypeInterface
  {
    // IATypeInterface의 인터페이스 함수 정의
    public string GetATypeName()
    {
      return "A Example";
    }
    // IBTypeInterface의 인터페이스 함수 정의
    public string GetBTypeName()
    {
      return "B Example";
    }
    // 출력 함수
    public void Print()
    {
      // 콘솔 출력
      Console.WriteLine("Example - GetATypeName - " + GetATypeName());
      Console.WriteLine("Example - GetBTypeName - " + GetBTypeName());
    }
  }
  // IATypeInterface 인터페이스를 상속받는다.
  class AExample : IATypeInterface
  {
    // IATypeInterface의 인터페이스 함수 정의
    public string GetATypeName()
    {
      return "A - AExample";
    }
    // 출력 함수
    public void Print()
    {
      // 콘솔 출력
      Console.WriteLine("AExample - GetATypeName - " + GetATypeName());
    }
  }
  // IBTypeInterface 인터페이스를 상속받는다.
  class BExample : IBTypeInterface
  {
    // IATypeInterface의 인터페이스 함수 정의
    public string GetBTypeName()
    {
      return "B - BExample";
    }
    // 출력 함수
    public void Print()
    {
      // 콘솔 출력
      Console.WriteLine("BExample - GetBTypeName - " + GetBTypeName());
    }
  }
  class Program
  {
    // 실행 함수
    public static void Main(string[] args)
    {
      // 인스턴스 생성
      Example ex = new Example();
      AExample ex1 = new AExample();
      BExample ex2 = new BExample();

      // IATypeInterface 인터페이스 그룹
      IATypeInterface[] aTypes = new IATypeInterface[]
      {
        ex,ex1
      };
      // IBTypeInterface 인터페이스 그룹
      IBTypeInterface[] bTypes = new IBTypeInterface[]
      {
        ex,ex2
      };
      // IATypeInterface 인터페이스 그룹 출력
      foreach (IATypeInterface a in aTypes)
      {
        a.Print();
      }
      // IBTypeInterface 인터페이스 그룹 출력
      foreach (IBTypeInterface b in bTypes)
      {
        b.Print();
      }

      // 아무 키나 누르시면 종료합니다.
      Console.WriteLine("Press any key...");
      Console.ReadLine();
    }
  }
}

위 예제 처럼 인스턴스를 세개를 생성하고 각 인터페이스 그룹에 따라 분류를 하고 Print함수를 호출할 수 있습니다.

using System;
namespace Example
{
  // IATypeInterface 인터 페이스
  interface IATypeInterface
  {
    // 메서드 정의
    string GetATypeName();
    void Print();
  }
  // IBTypeInterface 인터 페이스
  interface IBTypeInterface
  {
    // 메서드 정의
    string GetBTypeName();
    void Print();
  }
  // 위 두 개의 인터페이스를 상속받는다.
  class Example : IATypeInterface, IBTypeInterface
  {
    // IATypeInterface의 인터페이스 함수 정의
    public string GetATypeName()
    {
      return "A Example";
    }
    // IBTypeInterface의 인터페이스 함수 정의
    public string GetBTypeName()
    {
      return "B Example";
    }
    // 출력 함수
    public void Print()
    {
      // 콘솔 출력
      Console.WriteLine("Example - GetATypeName - " + GetATypeName());
      Console.WriteLine("Example - GetBTypeName - " + GetBTypeName());
    }
  }
  // IATypeInterface 인터페이스를 상속받는다.
  class AExample : IATypeInterface
  {
    // IATypeInterface의 인터페이스 함수 정의
    public string GetATypeName()
    {
      return "A - AExample";
    }
    // 출력 함수
    public void Print()
    {
      // 콘솔 출력
      Console.WriteLine("AExample - GetATypeName - " + GetATypeName());
    }
  }
  // IBTypeInterface 인터페이스를 상속받는다.
  class BExample : IBTypeInterface
  {
    // IATypeInterface의 인터페이스 함수 정의
    public string GetBTypeName()
    {
      return "B - BExample";
    }
    // 출력 함수
    public void Print()
    {
      // 콘솔 출력
      Console.WriteLine("BExample - GetBTypeName - " + GetBTypeName());
    }
  }
  class Program
  {
    // 파라미터에 따라 할당되는 클래스가 다르다.
    public static IATypeInterface GetATypeClass(bool type)
    {
      // true면 Example를 할당
      if (type)
      {
        return new Example();
      }
      // false면 Example를 할당
      else
      {
        return new AExample();
      }
    }
    // 실행 함수
    public static void Main(string[] args)
    {
      // true를 넣었기 때문에 Example 클래스의 인스턴스가 생성
      IATypeInterface aType = GetATypeClass(true);
      // Print함수 실행
      aType.Print();

      // 아무 키나 누르시면 종료합니다.
      Console.WriteLine("Press any key...");
      Console.ReadLine();
    }
  }
}

함수를 이용해서 파라미터 조건에 따라 인스턴스 생성을 달리 할 수 있습니다.

GetATypeClass함수에 true일 경우 Example 클래스의 인스턴스를 생성, false일 경우 AExample 클래스의 인스턴스 생성할 수 있습니다.


이처럼 인터페이스는 Heap 메모리에 인스턴스를 생성할 수는 없지만, 생성된 인스턴스를 인터페이스로 분류하여 객체를 정리, 정돈이 가능합니다.


여기까지 C#에서의 인터페이스(Interface)에 대한 글이었습니다.


궁금한 점이나 잘못된 점이 있으면 댓글 부탁드립니다.