프로그래밍Middle/Senior Kotlin 개발자

Kotlin에서 인터페이스를 통한 동작 위임(delegation by interface)은 어떻게 작동합니까? 언제 사용해야 하며, 속성 위임과 전통적인 상속과의 차이점은 무엇입니까?

Hintsage AI 어시스턴트로 면접 통과

답변.

Kotlin에서는 동작 위임이 "by"라는 키워드의 언어적 메커니즘을 통해 클래스의 시그니처에서 직접 구현됩니다. 이는 인터페이스(또는 여러 인터페이스)의 메소드 호출을 다른 구현 객체로 자동 전달하여 보일러플레이트를 줄이고 조합을 쉽게 만듭니다.

질문의 역사

인터페이스 위임의 출현은 다중 상속의 제한과 단점을 해결하려는 시도입니다. 이는 "상속보다 조합"이라는 아이디어로, 클래스 계층을 사용하지 않고 동작을 위임합니다. 조합이 더 일반적인 언어에서 차용되었습니다(예: Go, Scala).

문제

Java 및 다른 언어에서는 종종 인터페이스를 만들고 각 메소드를 수동으로 구현하여 로직을 다른 필드로 전달해야 하며(Object Adapter 패턴), 이는 메소드 수가 증가할 때 빠르게 구식이 됩니다.

해결책

Kotlin은 "by"를 사용하여 인터페이스를 선언적으로 위임할 수 있습니다:

interface Logger { fun log(msg: String) } class ConsoleLogger: Logger { override fun log(msg: String) = println(msg) } class Service(logger: Logger): Logger by logger { fun doWork() { log("작업 시작") // ... } } val service = Service(ConsoleLogger()) service.doWork()
  • 모든 Logger 메소드는 제공된 logger 객체를 통해 구현되며, Service 클래스에서는 메소드를 명시적으로 재정의하거나 프록시할 필요가 없습니다.

주요 특징:

  • 인터페이스 구현을 사용으로부터 분리하여 코드 중복을 줄일 수 있습니다.
  • 위임은 상속보다 유연하며 여러 동작에 대해 작동합니다.
  • SOLID의 모범 사례를 지원합니다.

잘못된 질문.

Service 클래스에 동일한 시그니처를 가진 메소드를 추가하면 어떻게 됩니까?

자체 구현이 "위임을 덮어씁니다"—즉, 클래스에서 명시적으로 정의된 메소드가 우선합니다:

class Service(logger: Logger): Logger by logger { override fun log(msg: String) = println("PREFIX: $msg") }

하나의 클래스가 서로 다른 객체에 여러 인터페이스를 위임할 수 있습니까?

네, 클래스는 서로 다른 객체에 여러 인터페이스를 구현하고 위임할 수 있지만, 각 인터페이스는 하나의 객체에만 위임됩니다:

class Service( logger: Logger, tracker: Tracker ): Logger by logger, Tracker by tracker

인터페이스 위임과 by를 통한 속성 위임의 차이점은 무엇입니까?

  • 인터페이스 위임은 인터페이스의 모든 기능 구현을 다른 객체로 전달합니다.
  • 속성 위임(property delegation)은 특정 타입의 delegated 객체에 대한 get/set의 작업을 위임합니다(예: ReadOnlyProperty, ReadWriteProperty).

일반적인 오류 및 안티 패턴

  • 너무 큰 인터페이스를 위임하는 것(ISP 위반)
  • 명시적 구현과 위임을 동시에 사용하는 것(예기치 않은 동작)
  • 위임과 상속을 부모 클래스와 결합하려고 시도하는 것, 메소드 해상 순서를 무시하며

실제 사례

부정적인 경우

클래스가 수동으로 인터페이스를 구현하고 각 메소드가 위임을 호출하며, 새로운 메소드가 추가될 때 프록시 업데이트를 잊어버려 오류가 발생합니다.

장점:

  • 로직이 명시적으로 제어됩니다.

단점:

  • 오류 위험이 높고, 보일러플레이트가 발생합니다.
  • 인터페이스가 커질 때 확장성이 좋지 않습니다.

긍정적인 경우

언어적 위임을 사용하고, 비표준 메소드만 클래스 내부에서 구현됩니다. 새로운 기능이 큰 수정 없이 추가됩니다.

장점:

  • 최소한의 코드
  • 확장 지점의 명확한 제어

단점:

  • 결합 구현 시 주의가 필요합니다(자체 구현으로 위임된 메소드를 쉽게 가릴 수 있음).