들어가기 전에
<자바의 신> p.243
같은 시점에 나온 맥북 프로의 4가지 모델이 있다고 하고, 각 모델을 처음부터 따로 디자인한다고 하면 엄청난 시간과 인건비가 소요될 것이다. 하지만, 하나를 제대로 만들어 놓고, 그것에서 파생되는 것들을 조금씩 바꾸어 판매하면 사용자는 기호에 따라 여러 모델을 살 수 있을 것이다. 자바에서의 상속도 바로 이와 비슷한 개념이다. 하나의 클래스를 잘 만들어 놓은 게 있으면, 그 클래스를 상속받아 내가 추가적인 기능을 넣을 수 있다.
자바에서는 코드의 재사용성을 위해 상속을 사용한다. 모든 클래스가 Object를 상속하는 이유가 Object에 클래스가 기본적으로 필요로 하는 메소드들을 모두 구현해 놓았기 때문인 것처럼, 하위 클래스가 필요로 하는 무언가를 상위 클래스가 가지고 있기 때문에 같은 내용의 코드를 중복해서 작성할 필요가 없기 때문이다. 거기다 Overriding을 통해 하위 클래스만의 기능을 추가할 수도 있으니 유연하게 활용할 수 있다.
Inheritance
상속은 아래처럼 IS-A, IS-KIND-OF-A로 나타낼 수 있는 관계에서 사용하면 된다.
public class Animal {
...
}
public class Lion extends Animal {
...
}
그런데 이렇게 좋아보이기만 하는 상속 관계의 문제점을 한 번에 파악할 수 있는 질문 하나가 있다.
java.util.Property extends Hashtable, java.util.Stack extends Vector 같은 클래스는 상속으로 망한 대표 사례입니다.
이유를 설명해 주세요.
위의 질문은 설로인에서 공개한 면접 질문 중 하나이다. 후자의 경우를 예로 들어 Stack과 Vector의 상속 관계에는 어떤 문제점이 있을까? Vector는 ArrayList와 기능이 유사한 확장 가능한 배열 형태이고 Thread safe하다는 특징이 있다. 반면에 Stack은 LIFO구조를 가진 자료구조이다. 물론 Stack을 배열로 구현할 수는 있지만 Stack 클래스가 Vector를 구체화했다고 이야기할 수 있을까? 거기다 Stack은 Thread safe하고 싶지 않아도 상위 클래스의 특징때문에 성능이 저하되는 단점을 끌어안고 가야 한다.
만약, 이 상황에서 Vector클래스 내부 기능이 변경되거나 더 이상 Thread safe하지 않게 된다면 어떻게 될까? Vector를 상속받고 있는 Stack클래스는 변경된 코드가 한 줄도 존재하지 않지만 결과적으로 변경 사항에 따라 작동하게 된다. 그게 Stack클래스가 의도하지 않은 동작이더라도 말이다. 이렇듯 잘못된 상속 사용은 클래스의 캡슐화를 깨뜨리고 상위 클래스의 구현 방식으로 인해 하위 클래스가 원치 않았던 안 좋은 영향을 끼치게 된다.
그래서 이러한 문제점을 만나지 않기 위해서는 클래스 간의 상속 관계가 부합하는지에 대해서도 신중하게 생각해야 하고, 상속으로 인해 문제가 생길 수 있는 부분들은 상속이 가능한 클래스를 만들기에 앞서서 문서를 기술하고 테스트를 통해 방지해야 한다.
그렇다면 상속 관계에 부합하지 않을 때는 어떤 경우일까? A 클래스가 B 클래스를 구성 요소로 포함하고 있으면서 B 클래스를 활용해서 기능의 일부나 전부를 정의하는 경우이다. 그리고 이럴 때 사용하는 것이 바로 조합(Composition) 방식이다.
Composition
컴포지션은 HAS-A 관계에서 사용하는 방식으로 다른 객체를 인스턴스 변수로 사용하면서 구현할 수 있다.
아래 코드는 Library클래스가 Book클래스를 인스턴스 변수로 가지고 메서드를 호출해서 사용한다.
public class Book {
...
public void read() {
...
}
}
public class Library {
...
Book book = new Book();
public void borrow() {
book.read();
...
}
}
이렇게 HAS-A 관계에서 컴포지션을 활용하면 상속과 어떻게 다를까? 기본적으로 상속을 하게 되면 상위 클래스 내부 구현이 모두 노출되면서 접근이 가능해진다. 하지만 위의 코드를 보면 다른 객체의 내부 구현이 노출될 필요 없이 public으로 설정된 메서드만을 호출해서 사용할 뿐이다. 그러므로 코드의 노출을 피하고 문제를 발생시킬 수 있는 접근을 막을 수 있는 다. 그리고 인스턴스 변수로 선언한 객체에 새로운 메서드가 추가되어도 아무런 영향을 받지 않고 서로의 캡슐화를 유지할 수 있다.
정리
- 상속(Inheritance)은 클래스 간의 관계가 IS-A이고 상위 클래스가 충분히 확장을 고려해서 설계되었을 때 코드의 재사용성을 높이기 위해 사용한다.
- 조합(Composition)은 클래스 간의 관계가 HAS-A일 때, 다른 객체의 내부 구현을 노출할 필요 없이 캡슐화를 유지하면서 사용할 수 있다.
- 무조건적인 사용보다 각각의 트레이드오프를 고려하여 적용해야 한다.
면접 예상 질문
- 상속의 장점과 단점은 무엇인가요?
- 상속과 조합은 각각 어떤 경우에 사용하게 되나요?
- Vector와 Stack의 상속 관계에서의 문제점은 무엇인가요?
참고
'Java' 카테고리의 다른 글
[Java] Socket에 관하여 (0) | 2023.02.08 |
---|---|
[Java] 다중 상속이 가지는 문제 (0) | 2023.02.03 |
[Java] 객체를 생성하는 다양한 방법 (0) | 2023.01.30 |
[Java] String의 불변성은 SCP로부터 (0) | 2023.01.27 |
[Java] Method Area, PermGen 그리고 Metaspace (0) | 2022.10.06 |