dynamic-dispatch

1 개의 포스트

코드 품질 개선 기법 24편: 유산의 가치 (새 탭에서 열림)

코드에서 로직은 동일하고 단순히 값만 달라지는 경우, 인터페이스와 상속을 사용하는 대신 데이터 클래스와 인스턴스 생성을 활용하는 것이 더 효율적입니다. 상속은 동적 디스패치나 의존성 역전과 같은 특정한 목적이 있을 때 강력한 도구가 되지만, 단순한 값의 차이를 표현하기 위해 사용하면 불필요한 복잡성을 초래할 수 있습니다. 따라서 객체의 속성 값만 변경되는 상황이라면 상속 불가능한 클래스를 정의하고 값만 다른 인스턴스를 만들어 사용하는 것이 코드의 가독성과 예측 가능성을 높이는 지름길입니다. **상속이 과용되는 사례와 문제점** * UI 테마(색상, 아이콘 등)를 적용할 때 인터페이스를 정의하고 각 테마(Light, Dark)별로 클래스를 상속받아 구현하는 방식은 흔히 발견되는 과잉 설계의 예시입니다. * 바인딩 로직이 모든 테마에서 동일함에도 불구하고 상속을 사용하면, 각 하위 클래스가 자신만의 고유한 로직을 가질 수 있다는 가능성을 열어두게 되어 코드 추적을 어렵게 만듭니다. * 특히 단순히 속성 값을 반환하는 목적일 때는 인터페이스를 통한 동적 디스패치가 성능상 미미한 오버헤드를 발생시킬 뿐만 아니라 구조를 복잡하게 만듭니다. **상속을 사용해야 하는 정당한 경우** * **로직의 동적 변경:** 상황에 따라 실행 시점에 로직 자체를 갈아끼워야 하는 동적 디스패치가 필요할 때 사용합니다. * **합 타입(Sum Types) 구현:** Kotlin의 `sealed class`처럼 정해진 하위 타입들의 집합을 정의해야 할 때 유용합니다. * **추상화와 구현의 분리:** 프레임워크의 제약 조건 대응, 의존성 주입(DI), 또는 빌드 속도 향상을 위해 인터페이스가 필요할 때 활용합니다. * **의존성 역전 법칙(DIP) 적용:** 순환 의존성을 해결하거나 의존성 방향을 단방향으로 유지해야 할 때 상속과 인터페이스를 사용합니다. **인스턴스 기반 모델링의 이점** * 인터페이스 대신 모든 속성을 포함하는 단일 클래스(Model)를 정의하고, 각 테마를 이 클래스의 인스턴스로 생성하면 구조가 훨씬 간결해집니다. * Kotlin의 기본 클래스처럼 상속이 불가능한(`final`) 클래스로 정의할 경우, 해당 인스턴스에 고유한 로직이 포함되지 않았음을 보장할 수 있습니다. * 속성 값이 변하지 않는다면 각 인스턴스는 데이터의 묶음으로서만 존재하게 되어, 상태 변화나 로직의 부수 효과를 걱정할 필요 없이 안전하게 재사용할 수 있습니다. 단순히 값만 다른 여러 타입을 구현해야 한다면 먼저 "상속이 반드시 필요한 로직의 차이가 있는가?"를 자문해 보시기 바랍니다. 로직이 같다면 클래스 상속보다는 데이터 모델의 인스턴스를 생성하는 방식이 유지보수하기 훨씬 쉬운 코드를 만들어줍니다.