인터페이스(Interface)
이번 강좌에서는 ‘인터페이스(Interface)’에 대해 알아보려고 합니다. 인터페이스라고 하니, 어디서 많이 들어보신것 같죠? 사전적 의미로는 ‘사용자인 인간과 컴퓨터를 연결하여 주는 장치. 키보드나 디스플레이 따위를 이른다.’, ‘서로 다른 두 시스템, 장치, 소프트웨어 따위를 서로 이어 주는 부분. 또는 그런 접속 장치.’라는 의미를 담고 있습니다. 예를 들어, 키보드나 모니터 등을 사용하기 위해서 USB 포트에 연결시키면 컴퓨터가 이를 자동으로 인식하여 바로 사용이 가능하게끔 만들어줍니다. 그 외에도 프린터, 모뎀, 스캐너 등과 같은 주변기기를 손쉽게 연결하여 컴퓨터 내에서 바로 사용할 수 있습니다. USB 역시도 하나의 인터페이스이며, 두 장치를 이어주어 컴퓨터와 주변 기기사이에서 정보를 주고 받을수 있도록 도와줍니다.
자, 이제 이런 인터페이스를 어떻게 정의하고 어떻게 사용되는지 예제를 통해 알아보도록 합시다. 우선 아래는 interface 키워드를 사용하여 인터페이스를 정의하는 예입니다.
interface 인터페이스명 { // ... }
인터페이스 내에서는 메소드, 이벤트, 인덱서, 속성이 쓰일 수 있으며, 필드를 포함할 수 없습니다. 그리고 인터페이스의 모든 멤버는 public로 접근 권한이 기본으로 지정됩니다. 대충 보아하니 클래스와 선언하는 방법이 비슷하죠? 맞습니다. 인터페이스는 말하자면 클래스의 한 종류라고도 말할 수 있습니다. 그러나 인터페이스 내에 쓰인 멤버는 구현부를 가지지 않습니다. 즉 몸통은 없이 정의되어 있어, 직접 호출할 수 없는 추상적인 멤버를 가집니다. 한가지 더 알아두셔야 할것이 있는데, 클래스의 상속에 대한 얘기를 다시한번 꺼내려고 합니다. 강좌를 거슬러 올라가서, 우리는 부모 클래스를 상속받은 자식 클래스는 여러개 존재할 수 있으나, 부모 클래스가 여러개 존재할 수 없습니다. 한마디로 말하자면 클래스는 다중 상속이 불가능하고 단일 상속만 할 수 있으나, 인터페이스 같은 경우는 다중 상속이 가능하다는 특징을 가지고 있습니다. 한번 지금까지 말한 인터페이스의 특징을 간략하게 정리해봅시다.
- 인터페이스 내에서는 메소드, 이벤트, 인덱서, 속성을 쓸 수 있습니다.
- 인터페이스에서는 필드를 포함할 수 없습니다.
- 모든 멤버는 public로 접근 권한이 기본으로 지정됩니다.
- 몸통이 정의되어 있지 않은 추상적인 멤버를 가집니다.
- 인터페이스는 다른 인터페이스를 상속하거나, 클래스에서 인터페이스 하나를 여러 차례 상속할 수 있습니다.
이제는 한번, 인터페이스를 직접 사용하여 보도록 합시다. 아래는 인터페이스에 관한 예제입니다.
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace ConsoleApplication32 { interface IMyInterfaceA { void print(); } interface IMyInterfaceB { void print(); } class MyClass:IMyInterfaceA, IMyInterfaceB { static void Main(string[] args) { MyClass mc = new MyClass(); IMyInterfaceA imca = mc; imca.print(); IMyInterfaceB imcb = mc; imcb.print(); } void IMyInterfaceA.print() { Console.WriteLine("IMyInterfaceA.print() 호출."); } void IMyInterfaceB.print() { Console.WriteLine("IMyInterfaceB.print() 호출."); } } }
IMyInterfaceA.print() 호출. IMyInterfaceB.print() 호출. 계속하려면 아무 키나 누르십시오 . . .
코드를 보시면, 9~11행과 14~17행에 각각 IMyInterfaceA, IMyInterfaceB라는 인터페이스가 정의되었습니다. 둘다 print란 멤버 함수가 존재합니다. 그리고 19행을 보시면 IMyInterfaceA와 IMyInterfaceB를 상속하고 있습니다. 그 다음에 25~29행을 보시면 인터페이스의 인스턴스를 통해 액세스 할 수 있음을 확인할 수 있습니다. 눈에 띄는건 동일한 함수를 구현하고자 할 때, 함수명 앞에 해당 인터페이스명을 명시하여 구현하고 있죠. 만약에 인터페이스명 없이 함수만 쓰였다면 어느것이 어떤 인터페이스의 메소드인지 구분할 수 없기 때문에 에러가 출력됩니다.
인터페이스는 인터페이스를 상속하고(Interface inherits interface)
앞에서 인터페이스의 특징을 설명하다보니 상속이란 말이 나오게 되었는데, 우리가 지금까지 알고있는건 클래스의 상속 뿐이었습니다. 클래스가 클래스를 상속할 수 있듯이, 인터페이스가 인터페이스를 상속할 수 있습니다. 형식은 클래스의 상속과 다르지 않습니다.
interface 자식인터페이스명 : 부모인터페이스명 { // ... }
아래 예제에서는 인터페이스를 인터페이스에 상속하고 다시 이 인터페이스를 클래스로 상속했습니다.
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace ConsoleApplication32 { interface ParentInterface { void myMethod(string str); } interface SubInterface: ParentInterface { void myMethod(string str, int i); } class MyClass: SubInterface { public void myMethod(string str) { Console.WriteLine(str + " ParentInterface.myMethod() call!"); } public void myMethod(string str, int count) { for (int i = 0; i < count; i ++) { Console.WriteLine(str + " SubInterface.myMethod() " + i + " call!"); } } } class Program { static void Main(string[] args) { MyClass sif = new MyClass(); sif.myMethod("Interface"); sif.myMethod("Inherits", 4); } } }
Interface ParentInterface.myMethod() call! Inherits SubInterface.myMethod() 0 call! Inherits SubInterface.myMethod() 1 call! Inherits SubInterface.myMethod() 2 call! Inherits SubInterface.myMethod() 3 call! 계속하려면 아무 키나 누르십시오 . . .
코드를 보시면 9~11행과 13~15행을 보시면 ParentInterface와 SubInterface가 정의되었고, 인터페이스 ParentInterface를 인터페이스 SubInterface에 상속시켰습니다. 그리고 17행을 보시면 이 SubInterface를 MyClass에 상속시켰습니다. 참고로 이제는 ParentInterface의 myMethod, SubInterface에 myMethod를 모두 물려받은 셈이 되겠습니다. 하지만 이것은 몸통이 없는 추상 멤버이므로 모두 빠짐없이 정의해 주어야 합니다. 꼭 기억하세요.