서론: 자바 개발자가 코틀린을 공부하면서
코틀린을 공부하면서 자바와는 다른 방식으로 인터페이스를 활용하는 점이 상당히 생소하게 느껴졌습니다. 자바의 인터페이스는 주로 구현해야 할 메서드를 정의하는 추상적인 설계도, 즉 규격의 역할을 담당합니다.
예를 들어 Spring AOP를 적용할 때 특정 인터페이스 기준으로 Aspect를 설정하고, 해당 인터페이스를 구현한 클래스들에만 선택적으로 공통 로직을 적용하는 용도로 활용할 수 있습니다. 이 경우 인터페이스는 구조를 식별하기 위한 경계로 사용됩니다.
반면 코틀린의 인터페이스는 이러한 규격의 역할에 머무르지 않습니다. 자바의 인터페이스는 "무엇을 구현해야 하는가"에 초점을 맞췄다면 코틀린의 인터페이스는 어떤 행위를 조합할 수 있는 가에 초첨을 두어 한 단계 확장되었습니다.
특히 OAuth2를 구현 함에 있어 코틀린의 인터페이스를 적극적으로 활용할 수 있습니다.
자바 방식
자바에서 OAuth2를 구현하면 다음과 같은 흐름일 것입니다.
1. `OAuth2UserInfo` 인터페이스를 만든다.
2. `OAuth2UserInfo`를 implements한 `GoogleUserInfo`, `NaverUserInfo` 클래스를 만든다.
3. 각 클래스에서 `getEmail()`, `getName()`등 직접 구현한다.
위 방식의 단점은 중복입니다. 구글이나 네이버의 데이터를 가져와서 정제하는 로직이 비슷해도, 각각 클래스 안에 중복해서 코드를 짜야 합니다. 아니면 중복을 피하기 위해 `Provider` 클래스를 생각할 수 있습니다. 그러나 해당 클래스에 각 서비스 별 메서드를 선언해야 한다는 점은 피할 수 없습니다.
코틀린 방식
코틀린의 핵심은 `by` 키워드를 이용한 위임입니다. 만약 OAuth2의 JSON 응답을 파싱하는 로직이 담긴 `Map` 기반의 구현체가 있다고 가정해 봅시다.
// 공통 규격 선언
interface OAuth2UserInfo {
val attributes: Map<String, Any>
fun getEmail(): String
fun getName(): String
}
// 공통 로직을 담은 구현체
class DefaultOAuth2UserInfo(
override val attributes: Map<String, Any>
) : OAuth2UserInfo {
override fun getEmail() = attributes["email"] as? String ?: ""
override fun getName() = attributes["name"] as? String ?: ""
}
// 실제 서비스별 구현 (직접 구현하지 않고 위임)
class GoogleUserInfo(
attributes: Map<String, Any>
) : OAuth2UserInfo by DefaultOAuth2UserInfo(attributes) {
// 추가로 구글에만 특화된 로직이 필요할 때만 override
override fun getName() = TODO(...)
}
이 방식을 사용하면 `GoogleUserInfo`는 `getEmail()`을 직접 구현하지 않아도 외부에서 이를 호출할 수 있습니다. 상속의 제약(단일 상속)에서 벗어나 여러 개의 구현체를 내 것처럼 조립할 수 있게 되었습니다.
🤔조립의 의미
코틀린의 인터페이스는 로직(Default Method)를 가질 수 있으며, 추상 프로퍼티를 정의할 수 있습니다. 이는 마치 필요한 기능을 블록처럼 클래스에 붙이는 경험을 제공합니다.
interface Validatable {
fun validateEmail(email: String) = email.contains("@")
}
interface Loggable {
val logTag: String
fun log(msg: String) = println("log[$logTag]: $msg")
}
// 여러 개의 인터페이스를 조립해서 기능을 확장
class OAuth2Service : Validatable, Loggable {
override val logTag = "OAuth2Service"
fun process(email: String) {
if (validateEmail(email) { // Validatable에서 가져온 기능
log("processing $email") // Loggable에서 가져온 기능
}
}
}
자바에서도 위처럼 다중 구현이 가능하지만 인터페이스의 상태에 가까운 프로퍼티를 정의하거나 위임을 사용하는 것이 훨씬 번거롭기 때문에 보통 `Service` 클래스에 모든 로직을 구현하거나, `Util`을 호출하는 방식을 사용했습니다.
하지만 코틀린은 인터페이스로 기능을 쪼개서 조립하는 방식을 권장합니다.
결론: 자바와 코틀린 관점 차이
자바를 주로 사용했던 개발자 입장에서 코틀린의 프로젝트의 인터페이스의 사용은 어색할 수 있습니다. 하지만 상속보다는 조립이라는 현대적인 객체지향 철학이 담겨있습니다.
자바에서 공통 로직의 중복을 피하기 위해 추상 클래스를 설계하거나 별도의 클래스로 로직을 빼서 관리했다면, 코틀린은 인터페이스 위임을 통해 모든 과정을 더 유연하게 구현할 수 있습니다. 인터페이스를 단순한 껍데기로 보는 것이 아닌 클래스에 강력한 기능을 부여하는 도구로 봐야 합니다.
| 구분 | 자바 인터페이스 | 코틀린 인터페이스 |
| 주요 역할 | 강제된 규격, 구조 식별 (AOP) | 기능의 위임 및 조립 |
| 중복 해결 | 추상 클래스 상속 또는 별도 Provider | `by` 키워드를 통한 위임 |
| 상태/로직 | 상수만 가능, default 메서드 활용 | 추상 프로퍼티, 풍부한 기본 구현 |
| 설계 철학 | 추상적인 설계도 | 장착, 조합 |
'Kotlin' 카테고리의 다른 글
| [Kotlin in action 2/e] 코틀린 타입 시스템 (0) | 2026.01.07 |
|---|---|
| [Kotlin in action 2/e] 람다로 프로그래밍 (0) | 2025.12.31 |
| [Kotlin in Action 2/e] 클래스, 객체, 인터페이스 (0) | 2025.12.30 |
| [Kotlin in Action 2/e] 함수 정의와 호출 (1) | 2025.12.30 |
| [Kotlin in Action 2/e] 코틀린의 기초 (0) | 2025.12.12 |