본문 바로가기
2학년 2학기/윈도우즈 프로그래밍

인터페이스(2) - 인터페이스, 다중상속

by kkkkk1023 2024. 9. 30.

1. 인터페이스(Interface)란 무엇인가?

인터페이스구현되지 않은 메소드, 프로퍼티, 인덱서, 이벤트로 구성된 설계의 표현입니다. 인터페이스는 구체적인 구현 없이 메소드 시그니처만 정의하며, 이 인터페이스를 구현하는 클래스가 그 메소드들을 구체적으로 구현해야 합니다.

주요 특징:

  • interface 키워드를 사용하여 선언.
  • 다중 상속이 가능함.
  • 모든 멤버는 public으로 접근 가능.
  • 인터페이스는 객체 자체를 가질 수 없으며, 구현되지 않은 기능만을 정의.

인터페이스 선언 예시:

interface IShape {
    void Draw();
    double Area();
}

 

 

 

2. 인터페이스의 구현

인터페이스는 클래스를 통해 구현되며, 인터페이스에 정의된 모든 멤버를 구현해야 합니다. 만약 하나라도 구현하지 않으면 그 클래스는 추상 클래스가 됩니다.

인터페이스 구현 예시:

class Rectangle : IShape {
    public void Draw() {
        Console.WriteLine("Drawing a rectangle");
    }

    public double Area() {
        return 20.0;
    }
}

 

 

 

3. 다중 상속과 인터페이스 확장

C#에서 인터페이스는 다중 상속이 가능합니다. 즉, 하나의 클래스가 여러 개의 인터페이스를 구현할 수 있으며, 인터페이스끼리도 확장될 수 있습니다.

다중 상속 예시:

interface IRectangle {
    void Area(int width, int height);
}

interface ITriangle {
    void Area(int width, int height);
}

class Shape : IRectangle, ITriangle {
    void IRectangle.Area(int width, int height) {
        Console.WriteLine("Rectangle Area: " + width * height);
    }

    void ITriangle.Area(int width, int height) {
        Console.WriteLine("Triangle Area: " + (width * height) / 2);
    }
}

 

 

4. 캐스팅을 통한 인터페이스 접근

인터페이스에서 선언된 멤버를 사용할 때 명시적으로 캐스팅하여 사용할 수 있습니다. 인터페이스가 동일한 이름의 메소드를 제공할 경우, 어떤 인터페이스의 메소드를 호출할 것인지 명확히 하기 위해 캐스팅이 필요합니다.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ImplementingInterfaceApp
{
    interface IRectangle
    {
        void Area(int width, int height);
    }
    interface ITriangle
    {
        void Area(int width, int height);
    }
    
    
    class Shape : IRectangle, ITriangle
    {
    //[명시적 인터페이스 구현]
        void IRectangle.Area(int width, int height)
        {
            Console.WriteLine("Rectangle Area : " + width * height);
        }
        void ITriangle.Area(int width, int height)
        {
            Console.WriteLine("Triangle Area : " + width * height / 2);
        }
    }
    
    
    class Program
    {
        public static void Main()
        {
            Shape s = new Shape();
            // s.Area(10, 10);             // error - ambiguous !!!
            // s.IRectangle.Area(10, 10);  // error
            // s.ITriangle.Area(10, 10);   // error
            ((IRectangle)s).Area(20, 20);  // 캐스팅-업
            ((ITriangle)s).Area(20, 20);   // 캐스팅-업
            IRectangle r = s;              // 인터페이스로 캐스팅-업
            ITriangle t = s;               // 인터페이스로 캐스팅-업
            r.Area(30, 30);
            t.Area(30, 30);
        }
    }
}

 

캐스팅 방법은 두 가지로 나눌 수 있습니다. 두 방법 모두 인터페이스를 통해 명시적 인터페이스 구현 메소드를 호출할 수 있게 합니다. 차이점은 즉시 캐스팅이냐 변수에 할당 후 캐스팅이냐에 있습니다.

 

 

1. 즉시 캐스팅 (Inline Casting)

 

이 방법은 한 줄에서 직접 객체를 캐스팅하고, 그 캐스팅된 인터페이스를 통해 메소드를 호출하는 방식입니다.

예시:

((IRectangle)s).Area(20, 20);  // 즉시 캐스팅하여 메소드 호출

설명:

  • (IRectangle): s 객체를 IRectangle 인터페이스로 캐스팅합니다.
  • 캐스팅된 객체로 즉시 메소드 호출이 가능합니다.
  • 한 줄에서 캐스팅과 호출이 동시에 이루어지며, 이 방식은 일시적으로만 해당 인터페이스 타입으로 객체를 사용할 때 주로 사용됩니다.

 

2. 변수에 할당 후 캐스팅

 

이 방법은 객체를 먼저 인터페이스 타입의 변수캐스팅하여 저장한 후, 해당 변수를 사용하여 인터페이스 메소드를 호출하는 방식입니다.

예시:

IRectangle r = s;  // 객체를 IRectangle 타입으로 캐스팅하여 저장
r.Area(30, 30);    // 변수를 통해 메소드 호출

설명:

  • IRectangle r = s;: s 객체를 IRectangle 타입으로 캐스팅하고, 그 결과를 r 변수에 저장합니다.
  • 이후 r 변수를 통해 메소드를 호출할 수 있습니다.
  • 이 방법은 여러 번 해당 인터페이스의 메소드를 호출하거나, 캐스팅된 객체를 반복적으로 사용할 경우에 적합합니다.

두 방식의 차이점

방법 설명 사용 시점
즉시 캐스팅 (Inline Casting) 한 줄에서 즉시 객체를 캐스팅하고 메소드를 호출합니다. 한 번만 메소드를 호출하거나 일시적으로만 사용할 때 적합
변수에 할당 후 캐스팅 객체를 변수에 캐스팅하여 저장하고, 여러 번 호출하거나 반복적으로 사용할 때 유용합니다. 여러 번 해당 인터페이스 메소드를 호출할 필요가 있을 때 적합

 

 

예시 비교:

1. 즉시 캐스팅 (Inline Casting)

((IRectangle)s).Area(20, 20);  // 캐스팅 후 즉시 메소드 호출

2. 변수에 할당 후 캐스팅

IRectangle r = s;  // 객체를 인터페이스 타입으로 캐스팅하여 변수에 저장
r.Area(30, 30);    // 변수를 사용하여 메소드 호출

언제 각각의 방법을 사용할까?

  • 즉시 캐스팅간단한 상황에서, 특정 메소드를 한 번만 호출할 때 주로 사용됩니다.
    • 예를 들어, 특정 메소드를 한 번만 실행하고 더 이상 사용할 일이 없는 경우에 적합합니다.
  • 변수에 할당 후 캐스팅같은 객체를 반복적으로 사용하거나, 인터페이스 타입의 메소드를 여러 번 호출할 때 사용됩니다.
    • 변수를 사용하면 코드 가독성도 좋아지고, 반복적인 캐스팅을 피할 수 있습니다.

 

 

요약:

두 방식 모두 인터페이스를 통해 명시적 구현 메소드를 호출할 수 있으며, 선택은 상황에 따라 달라집니다.

  • 즉시 캐스팅: 객체를 한 번만 캐스팅하고 바로 메소드를 호출할 때 적합.
  • 변수에 할당 후 캐스팅: 여러 번 메소드를 호출하거나 캐스팅된 객체를 반복적으로 사용할 때 적합.

 

💡 캐스팅이 필요한 이유

: 캐스팅을 통해서만 메소드에 접근할 수 있는 이유는, 명시적 인터페이스 구현을 사용하면 클래스 자체의 인스턴스에서 해당 메소드가 보이지 않기 때문입니다. 즉, 인터페이스 타입으로 캐스팅하지 않으면 클래스 인스턴스는 이 메소드를 직접 호출할 수 없습니다.

 

 

 

5. 다이아몬드 상속 문제

인터페이스에서 다중 상속이 가능하기 때문에, 다이아몬드 상속 문제가 발생할 수 있습니다. 예를 들어, 인터페이스 IX를 상속한 두 클래스가 IX의 메소드를 재정의할 때, 그 하위 클래스는 어느 버전의 메소드를 사용할지 혼란이 발생할 수 있습니다.

다이아몬드 상속 예시:

interface IX { void XMethod(int i); }
interface IY : IX { void YMethod(int i); }

class A : IX {
    public void XMethod(int i) { /* ... */ }
}

class B : A, IY {
    public void YMethod(int i) { /* ... */ }
}

 

 

 

6. 인터페이스와 추상 클래스의 차이점

  • 인터페이스는 오직 메소드 시그니처만 제공하고, 다중 상속을 지원하며, 메소드 구현 시 override 키워드를 사용할 수 없습니다.
  • 추상 클래스는 단일 상속만 지원하며, 메소드의 일부 구현을 제공할 수 있고, 메소드 재정의 시 override 키워드를 사용해야 합니다.

 

 

 

7. 네임스페이스(Namespace)

네임스페이스는 관련된 클래스, 인터페이스, 구조체 등을 그룹화하는 단위입니다. 네임스페이스는 클래스 이름 충돌을 방지하는 데 유용하며, 다양한 코드를 모듈화하여 관리할 수 있습니다.

네임스페이스 사용 예시:

namespace ShapesNamespace {
    class Circle {
        public void Draw() {
            Console.WriteLine("Drawing a Circle");
        }
    }
}