상속(Inheritance)의 결함
The Flaws of Inheritance by CodeAesthetic
상속의 결함과 객체 지향 설계의 대안: 구성(Composition)
객체 지향 프로그래밍에서 코드 재사용을 위해 흔히 사용되는 상속의 구조적 한계를 분석하고, 그 대안으로 제시되는 구성과 인터페이스를 통한 유연한 설계 방식을 상세히 설명합니다.
-
상속(Inheritance)의 정의와 작동 방식
- 상속은 기존 클래스의 기능을 재사용하기 위해 자식 클래스를 생성하여 기능을 확장하는 방식입니다.
- 부모 클래스를 확장하면 사실상 동일한 복사본을 만드는 것과 같으며, 여기에 새로운 메서드를 추가하거나 기존 메서드를 오버라이드하여 동작을 변경합니다.
- 예시: 이미지 라이브러리 설계
- 이미지 데이터를 메모리에 저장하고 픽셀 값을 조회하는 기본 이미지 클래스가 존재합니다.
- 크기 조절(Resize), 가로/세로 뒤집기(Flip) 등의 공통 메서드를 포함합니다.
- 다양한 파일 형식(JPEG, PNG, BMP)을 지원하기 위해
save,load라는 추상 메서드를 추가합니다. - 각 파일 형식은 이미지 클래스를 상속받아 고유의 저장 및 불러오기 기능을 구현하면서 공통 기능을 무료로 물려받습니다.
-
상속의 문제점: 강한 결합과 변화에 대한 취약성
- 강한 결합(Coupling) : 상속은 자식 클래스를 부모 클래스의 구조에 종속시킵니다. 부모의 구조가 자식에게 강제로 주입되는 결과를 초래합니다.
- 부적절한 메서드 강제: 파일과 관련 없는 '그릴 수 있는 이미지(Drawable Image)' 클래스를 만들 때도 상속을 사용하면, 필요 없는
load와save메서드를 강제로 구현해야 합니다. - 예외 상황 처리의 어려움: 필요 없는 메서드에 대해 예외를 던지는 식으로 대응할 수 있지만, 이는 근본적인 해결책이 아닙니다.
- 리팩토링 비용: 구조를 고치기 위해 중간 단계의 부모 클래스(예: File Image 클래스)를 추가하면, 기존에 이미지 클래스를 기대하던 모든 코드를 수정해야 하는 막대한 비용이 발생합니다.
- 설계의 고착화: 상속은 모든 공통 요소를 부모 클래스에 몰아넣도록 유도하지만, 단 하나의 예외만 발생해도 전체 설계가 무너집니다.
-
구성(Composition)의 개념과 장점
- 구성의 정의: 상속 없이 코드를 재사용하는 패턴으로, 클래스 내부에서 다른 클래스의 인스턴스를 사용하는 방식입니다.
- 독립적 설계: 이미지 클래스에서 추상 메서드를 제거하고 순수하게 메모리상의 이미지만 표현하게 만듭니다.
- 객체 주입: JPEG나 PNG 클래스는 상속 대신 이미지 객체를 인자로 전달받아 처리합니다.
- 유연한 조합: 사용자는 필요한 클래스들을 조합하여 기능을 수행합니다.
- 예: JPEG 이미지를 불러와서(Load), 그림을 그리고(Draw), 다시 저장(Save)하는 과정을 각 클래스의 조합으로 해결합니다.
- 영향 범위 최소화: 새로운 기능을 추가하더라도 기존의 다른 클래스들을 수정할 필요가 없습니다.
-
인터페이스(Interface)와 추상화
- 상속은 '코드 재사용'과 '추상화'라는 두 가지 능력을 결합하고 있습니다.
- 인터페이스의 역할: 클래스의 모든 변수와 메서드를 공유하는 상속과 달리, 인터페이스는 객체가 할 수 있는 일에 대한 최소한의 계약만을 정의합니다.
- 가벼운 추상화: 인터페이스를 사용하면 구체적인 클래스 타입을 몰라도
load나save같은 메서드를 호출할 수 있습니다. 이는 기존 클래스에 쉽게 붙일 수 있는 훨씬 가벼운 방식입니다. - 의존성 주입(Dependency Injection) : 클래스 내부에서 구체적인 타입을 결정하는 대신, 외부에서 인터페이스를 통해 필요한 객체를 전달받는 방식입니다. 이를 통해 UI 처리와 파일 처리를 완벽히 분리할 수 있습니다.
-
구성의 단점과 상속이 유용한 경우
- 구성의 단점:
- 내부 타입을 초기화하기 위한 상속 코드(Boilerplate) 가 많이 발생할 수 있습니다.
- 여러 구현체에서 동일한 코드가 반복될 가능성이 있습니다.
- 내부 객체의 정보를 외부로 노출하기 위해 단순 호출만 수행하는 래퍼 메서드를 많이 만들어야 할 수 있습니다.
- 상속이 유용한 경우:
- 이미 상속 구조로 짜여진 기존 시스템 내에서 작업할 때 유용합니다.
- 수백 개의 클래스가 동일한 규격을 따라야 하고 그중 절반 이상이 반복적인 코드를 가질 때, 전체 구조를 바꾸는 비용이 너무 크다면 현실적인 타협안이 될 수 있습니다.
- 구성의 단점:
-
상속을 안전하게 사용하는 방법
- 직접 접근 제한: 부모 클래스의 변수에 자식이 직접 접근하는
protected변수 사용을 지양해야 합니다. - 명시적 API 구축: 오버라이드하거나 접근해야 하는 기능은 명시적인
protected메서드(API)로 제공합니다. - 폐쇄적 설계: 그 외의 모든 것은
private,final, 또는sealed로 마킹하여 자식 클래스가 부모의 내부 동작을 오해하여 버그를 만드는 것을 방지해야 합니다.
- 직접 접근 제한: 부모 클래스의 변수에 자식이 직접 접근하는
토픽:
프로그래밍