인터페이스 위임(Interface Delegation)

인터페이스 위임(Interface Delegation)

인터페이스의 메서드를 클래스 자신이 구현하지 않고, 도우미(헬퍼) 객체에게 위임하고 싶을 때가 있어요. 예를 들어 서로 전혀 관련 없는 클래스들에 같은 인터페이스 기능을 추가하고 싶다면, 그 기능을 별도 클래스로 만들어 두고 각 클래스가 그 인스턴스를 쓰는 편이 깔끔하죠. 이때 쓰는 게 implements 수식어예요.

출처: Interface delegation

본문

때로는 인터페이스의 메서드를 헬퍼(또는 위임) 객체가 구현할 때가 있어요. 또는 클래스 인스턴스가 그 인터페이스에 대한 인터페이스 포인터를 얻어 와서 그것을 사용해야 할 때도 있어요. 예를 들어 전혀 무관한 클래스들에 인터페이스를 추가해야 하는 상황을 생각해 봐요. 필요한 인터페이스 기능을 별도 클래스에 넣고, 각 클래스가 그 도우미 클래스의 인스턴스를 이용해 기능을 구현하는 방식이에요.

이런 경우 컴파일러에게 "이 인터페이스는 객체 자신이 구현하는 게 아니라 도우미 클래스나 인터페이스에 있다"고 알려줄 수 있어요. 그게 바로 implements 프로퍼티 수식어예요.

클래스가 원하는 인터페이스에 대한 포인터를 갖고 있다면, 아래처럼 쓰면 IMyInterface가 요청될 때 그 필드의 참조를 사용하라고 컴파일러에 지시할 수 있어요.

type
   IMyInterface = interface
     procedure P1;
   end;

   TMyClass = class(TInterfacedObject, IMyInterface)
   private
     FMyInterface: IMyInterface; // interface type
   public
     property MyInterface: IMyInterface
        read FMyInterface implements IMyInterface;
   end;

인터페이스가 꼭 필드에 있을 필요는 없어요. read 지정자로 쓸 수 있는 어떤 식별자든 사용할 수 있어요.

인터페이스를 위임 객체(실제로 인터페이스를 구현하는 헬퍼 객체)가 구현한다면, 그것도 implements 키워드로 쓸 수 있어요.

{$interfaces corba}
 type
   IMyInterface = interface
     procedure P1;
   end;

   // NOTE: Interface must be specified here
   TDelegateClass = class(TObject, IMyInterface)
   private
     procedure P1;
   end;

   TMyClass = class(TInterfacedObject, IMyInterface)
   private
     FMyInterface: TDelegateClass; // class type
     property MyInterface: TDelegateClass
       read FMyInterface implements IMyInterface;
   end;

여기서 Delphi와 다른 점 하나를 알아두세요. 위임 클래스는 인터페이스를 명시적으로 지정해야 해요. 컴파일러는 위임 클래스 안에서 메서드를 찾아보지 않고, 위임 클래스가 지정된 인터페이스를 구현하는지 그저 확인만 해요.

하나의 위임 객체로 여러 인터페이스를 구현할 수도 있어요.

{$interfaces corba}
 type
   IMyInterface = interface
     procedure P1;
   end;
   IMyInterface1 = interface
     procedure P2;
   end;

   // NOTE: Interface must be specified here
   TDelegateClass = class(TObject, IMyInterface,IMyInterface1)
   private
     procedure P1;
     procedure P2;
   end;

   TMyClass = class(TInterfacedObject, IMyInterface, IMyInterface1)
   private
     FMyInterface: TDelegateClass; // class type
     property MyInterface: TDelegateClass
       read FMyInterface implements IMyInterface,IMyInterface1;
   end;

메서드 해석과 인터페이스 위임을 섞는 것은 불가능해요. 즉, 인터페이스의 일부는 메서드 해석으로, 일부는 위임으로 구현하는 건 안 돼요. 다음 코드는 IMyInterface를 일부는 메서드 해석(P1)으로, 일부는 위임으로 구현하려고 하는데, 컴파일러가 받아들이지 않아요.

{$interfaces corba}
 type
   IMyInterface = interface
     procedure P1;
     procedure P2;
   end;


   TMyClass = class(TInterfacedObject, IMyInterface)
     FI : IMyInterface;
   protected
     procedure IMyInterface.P1 = MyP1;
     procedure MyP1;
     property MyInterface: IMyInterface  read FI implements IMyInterface;
   end;

컴파일러는 에러를 던져요.

Error: Interface "IMyInterface" can't be delegated by "TMyClass",
 it already has method resolutions

하지만 한 인터페이스는 메서드 해석으로, 또 다른 인터페이스는 위임으로 구현하는 건 가능해요.

{$interfaces corba}
 type
   IMyInterface = interface
     procedure P1;
   end;

   IMyInterface2 = interface
     procedure P2;
   end;

   TMyClass = class(TInterfacedObject,
                    IMyInterface, IMyInterface2)
     FI2 : IMyInterface2;
   protected
     procedure IMyInterface.P1 = MyP1;
     procedure MyP1;
   public
     property MyInterface: IMyInterface2
        read FI2 implements IMyInterface2;
   end;

인터페이스 위임은 부모 인터페이스도 구현한다고 지정하는 데 쓸 수 있어요.

IGMGetFileName = interface(IUnknown)
   ['{D3ECCB42-A563-4cc4-B375-79931031ECBA}']
   function GetFileName: String; stdcall;
   property FileName: String read GetFileName;
 end;

 IGMGetSetFileName = Interface(IGMGetFileName)
   ['{ECFB879F-86F6-41a3-A685-0C899A2B5BCA}']
   procedure SetFileName(const Value: String); stdcall;
   property FileName: String read GetFileName write SetFileName;
 end;

 TIntfDelegator = class(TInterfacedObject, IGMGetFileName, IGMGetSetFileName)
  protected
   FGetSetFileName: IGMGetSetFileName;
  public
   constructor Create;
   destructor Destroy; override;
   property Implementor: IGMGetSetFileName read FGetSetFileName
     implements IGMGetFileName, IGMGetSetFileName;
 end;

더 알아보기

  • 메서드 해석(별칭) 방식은 7.3 절(인터페이스 구현)에서 다뤄요.
  • 인터페이스 상속은 7.4 절에서 확인할 수 있어요.
  • implements 수식어가 쓰이는 프로퍼티 정의는 6.7 절(프로퍼티)을 참고해요.