코틀린에서 추상 클래스 (abstract class)와 인터페이스 (interface)는 추상적인 로직을 선언하는 데 사용되지만, 이들 사이에는 중요한 차이점이 있습니다:
abstract class Animal(val name: String) { abstract fun makeSound() fun info() = println("저는 $name 입니다.") } interface Movable { fun move() } class Cat(name: String) : Animal(name), Movable { override fun makeSound() = println("야옹") override fun move() = println("고양이가 움직입니다") }
코틀린에서 인터페이스가 상태(백킹 필드)를 가질 수 있습니까?
대부분의 사람들은 인터페이스에 속성을 선언할 수 있다고 대답하지만, 그것들은 여전히 백킹 필드를 가지지 않으며, 값 저장 없이 게터/세터만 존재합니다. get() = ...를 작성하더라도 값은 매번 계산됩니다.
예시:
interface MyInterface { val value: Int get() = 42 // 값 저장 없음, 실시간 계산 }
이야기
대규모 프로젝트에서 선임 개발자가 인터페이스에 상태를 저장하려고 했습니다(예: 카운터), var count: Int 속성이 백킹 필드를 가질 것이라고 예상했습니다. 그 결과 인터페이스를 구현하는 모든 클래스에서 값을 저장하는 방법이 다르게 구현되어 불안정한 로직이 발생했습니다(세터를 재정의하는 것을 잊으면 데이터가 손실되었습니다).
이야기
한 개발자가 다양한 행동 전략(전략 패턴)을 구현해야 할 곳에서 추상 클래스를 사용했습니다. 결과적으로 발생한 문제는 한 기본 클래스만 상속할 수 있었지만, 다양한 행동 측면에 대해 조합이 필요하다는 점이었습니다. 인터페이스로 아키텍처를 변경해야 했습니다.
이야기
팀은 두 개의 추상 클래스를 상속하려고 했습니다(하나는 로직을 위한 것, 다른 하나는 공통 유틸리티를 위한 것), 이는 코틀린에서는 불가능합니다. 문제는 애플리케이션 확장 단계에서 발견되었습니다. 이로 인해 클래스를 급히 재조직해야 하여 상당한 기술적 부채가 발생했습니다.