상속은 코드를 공유하는 수단이 아니다. 공통된 기능을 여러 객체에 전달하고 싶을 때 상속을 쓴다는 것이 가장 흔한 오해이고, 상속은 추상과 구체의 관계일 때 쓰는 것이 맞다. 이 오해에서 시작된 상속이 나중에 LSP 위반과 강한 결합으로 돌아온다.

상속이 만드는 결합

상속은 부모의 구현에 자식을 묶어버린다. 부모가 바뀌면 자식이 전부 영향을 받는다. 공통 코드를 모으려고 만든 부모는 원래 하나의 개념이 아니었으므로, 나중에 어느 자식에게만 필요한 변경이 생기면 부모가 이상해지기 시작한다. 상속이 객체 사이에서 가장 강한 결합이라 반드시 써야 할 때만 쓰라는 말이 여기서 나온다.

자바는 다중 상속도 지원하지 않는다. 두 부모에 같은 이름의 메서드가 있으면 어느 쪽을 물려받을지 모호해지는 다이아몬드 문제 때문이다. 코드를 재사용하려고 상속 자리를 써버리면 정작 필요한 곳에 상속을 쓸 수 없다.

필드로 두고 위임하기

합성은 상속 대신 가지고 있게 만드는 것이다. 필요한 객체를 필드로 두고 그 일을 위임한다.

부모의 구현에 묶이지 않고, 런타임에 바꿔 끼울 수 있다. 상속은 컴파일 시점에 고정된다. 여러 개를 가질 수 있으므로 다중 상속 제약도 받지 않는다.

Input과 Output 구현체를 묶어 Console을 만들 때 상속이 아니라 합성을 쓰라는 조언이 이 이유에서 나온다.

is-a와 has-a

“B는 A다”가 자연스러우면 상속, “B는 A를 가진다”가 자연스러우면 합성이다. 앞이 is-a 관계이고 뒤가 has-a 관계다.

Dog is a Animal은 상속이다. Car has an Engine은 합성이다. 자동차를 엔진의 자식으로 만들면 어색한 것이 그 때문이다.

학생과 과목도 같은 함정이다. 모든 학생이 전공 과목을 가지니 Subject의 메서드를 쓰려고 상속받고 싶어지지만, 과목이 학생을 포괄하는 개념이 아니므로 상속이 아니다. SubjectStudent의 멤버 변수로 두는 것이 맞다.

관련

출처