인터페이스 상속(Interface Inheritance)

인터페이스 상속(Interface Inheritance)

인터페이스도 클래스처럼 서로 상속할 수 있어요. 부모 인터페이스의 메서드가 모두 물려받아지니까, 자식 인터페이스를 구현하는 클래스는 부모의 메서드까지 전부 채워야 해요. 여기까진 직관적이죠. 그런데 이 상속 관계가 컴파일러의 '선언된 인터페이스 표'와 얽히면서 조금 주의할 점이 생겨요.

출처: Interface inheritance

본문

한 인터페이스가 다른 인터페이스의 자식(descendent)이 되게 할 수 있어요.

IParentInterface = interface
   ['{0F78D56E-85A6-4024-98D7-720D7C7B9573}']
   procedure Foo;
 end;

 IChildInterface = interface(IParentInterface)
   ['{1AB2EB85-6843-462E-8CE4-32ECC065011E}']
   procedure Bar;
 end;

이 경우 IChildInterface는 foo와 bar 두 개의 메서드를 가지게 돼요. 그래서 이 인터페이스를 구현하는 클래스는 두 인터페이스 모두를 구현해야 해요.

TImplementor = class(TInterfacedObject, IChildInterface)
 public
   procedure Foo;
   procedure Bar;
 end;

 procedure TImplementor.Foo;
 begin

 end;

 procedure TImplementor.Bar;
 begin

 end;

여기서 주의할 점이 있어요. 클래스가 자식 인터페이스를 선언하면, 그 클래스는 자식 인터페이스 타입의 변수에 할당될 수 있어요. 위 선언을 기준으로 아래 코드는 컴파일돼요.

var
   Child: IChildInterface;

 begin
   Child := TImplementor.Create;

하지만 이것이 자동으로 부모 인터페이스 타입의 변수와도 할당 호환된다는 뜻은 아니에요. 아래 코드는 컴파일되지 않아요.

var
   Parent: IParentInterface;

 begin
   Parent := TImplementor.Create;

이걸 컴파일되게 하려면 클래스를 이렇게 선언해야 해요.

TImplementor = class(TInterfacedObject,
                     IParentInterface,
                     IChildInterface)
 public
   procedure Foo;
   procedure Bar;
 end;

이유가 있어요. 클래스가 실제로 IParentInterface의 메서드를 구현하고 있더라도, 컴파일러는 할당 호환성을 검사할 때 실제로 선언된 인터페이스만 확인해요. 모든 선언된 인터페이스는 테이블에 들어가고, 이 테이블의 내용만 검사되죠. 같은 검사가 런타임에도 수행돼요. 컴파일러는 클래스가 선언하는 모든 인터페이스로 테이블을 만들고, 런타임에 이 테이블을 확인해요.

즉, IChildInterface만 선언했을 때 아래 코드는 컴파일되지만,

ParentImplementorInstance := (TImplementor.Create as IParentInterface);

여전히 런타임 에러로 실패해요.

home:~> ./ti
 An unhandled exception occurred at $0000000000411A27:
 EInvalidCast: Invalid type cast
 $0000000000411A27

그래서 부모 인터페이스까지 할당 호환되게 하려면 클래스 선언에 부모 인터페이스도 명시적으로 나열해 주는 게 핵심이에요.

더 알아보기

  • 인터페이스의 기본 정의는 7.1 절을 참고해요.
  • 인터페이스 메서드 연결은 7.3 절에서 다뤄요.
  • 인터페이스 위임은 7.5 절에서 확인할 수 있어요.