composition-over-inheritance

1 개의 포스트

코드 품질 개선 기법 13편: 클론 가족 (새 탭에서 열림)

두 개의 상속 트리가 서로 암묵적으로 대응하며 발생하는 '클론 가족' 문제는 코드의 타입 안정성을 해치고 유지보수를 어렵게 만듭니다. 이 글은 코드 공통화를 위한 부적절한 상속 사용을 경계하고, 대신 컴포지션이나 제네릭을 활용하여 데이터 모델 간의 관계를 명확히 정의함으로써 런타임 오류 가능성을 줄이는 방법을 제시합니다. ### 클론 가족 현상과 타입 안정성 문제 데이터를 공급하는 클래스 계층과 데이터 모델 계층이 서로 일대일로 대응하지만, 이 관계가 코드상에서 명시되지 않을 때 문제가 발생합니다. * **다운캐스팅의 필요성**: 부모 공급자 클래스가 공통 데이터 모델 인터페이스를 반환할 경우, 실제 사용 시점에서는 구체적인 타입으로 변환하는 다운캐스팅(`as`)이 강제됩니다. * **암묵적 제약 조건**: '특정 공급자는 특정 모델만 반환한다'는 규칙이 컴파일러가 아닌 개발자의 머릿속에만 존재하게 되어, 코드 변경 시 실수로 인한 오류가 발생하기 쉽습니다. * **유연성 부족**: 하나의 공급자가 여러 모델을 반환하거나 구조가 복잡해질 때, 타입 검사만으로는 시스템의 안전성을 보장하기 어렵습니다. ### 상속 대신 애그리게이션과 컴포지션 활용 단순히 로직의 공통화가 목적이라면 상속보다는 기능을 분리하여 포함하는 방식이 더 효과적입니다. * **로직 추출**: 공통으로 사용하는 데이터 획득 로직을 별도의 클래스(예: `OriginalDataProvider`)로 분리합니다. * **의존성 주입**: 각 공급자 클래스가 분리된 로직 클래스를 속성으로 가지도록 설계하면, 부모 클래스 없이도 코드 중복을 피할 수 있습니다. * **타입 명확성**: 각 공급자가 처음부터 구체적인 데이터 타입을 반환하므로 다운캐스팅이 아예 필요 없어집니다. ### 제네릭을 이용한 매개변수적 다형성 적용 여러 공급자를 하나의 컬렉션으로 관리해야 하는 등 상속 구조가 반드시 필요한 경우에는 제네릭을 통해 타입을 명시해야 합니다. * **타입 파라미터 지정**: 부모 클래스에 타입 파라미터 `T`를 도입하여, 자식 클래스가 어떤 타입의 데이터를 반환하는지 컴파일 시점에 명시하도록 합니다. * **상한 제한(Upper Bound)**: 필요한 경우 `T : CommonDataModel`과 같이 제약 조건을 추가하여 최소한의 공통 인터페이스를 보장할 수 있습니다. * **업캐스팅 지원**: 제네릭을 사용하면 부모 타입으로 관리하면서도 각 인스턴스가 반환하는 타입의 안전성을 유지할 수 있어 활용도가 높습니다. 상속은 강력한 도구이지만 단순히 코드를 재사용하기 위한 목적으로 사용하면 의도치 않은 타입 문제를 야기할 수 있습니다. 클래스 간의 관계가 암묵적인 제약에 의존하고 있다면, 이를 컴포지션으로 분리하거나 제네릭을 통해 명시적인 관계로 전환하는 것이 견고한 코드를 만드는 핵심입니다.