•
먼저 우리는 다음과 같은 이유로 상속을 사용한다.
◦
코드를 재사용함으로써 중복을 줄이기 위해
◦
변화에 대한 유연성 및 확장성을 증가시키기 위해
◦
개발 시간을 단축시키기 위해
•
하지만 상속의 단점들은 상속을 적절하게 사용했을 때만 해당한다. 상속을 잘못 사용한다면 이는 오히려 독으로 작용한다.
•
상속을 적절하게 사용하지 못했을 때의 단점들을 살펴보자.
상속의 단점
•
캡슐화를 깨뜨린다.
◦
상위 클래스의 구현이 하위 클래스에 노출될 수 밖에 없는 상속은 캡슐화를 무너뜨린다.
◦
캡슐화가 무너짐으로써 하위 클래스가 상위 클래스에 강하게 결합, 의존하게 되며 이는 곧 유연한 대처가 불가능해짐을 의미한다.
◦
즉 상속이 캡슐화에 있어서 문제를 일으키게되는 경우는 변경사항이 생겼을 때, 강하게 결합되어 있어 유연하게 대응하기 어렵기 때문이다.
public class Human {
public String name;
public void introduceSelf() {
System.out.println("My Name is " + name);
}
}
Java
복사
public class Korean extends Human {
public Korean(String name) {
super(name);
}
}
Java
복사
◦
만약 Human 클래스의 name 멤버 변수의 타입이 변경되는 경우, Human 클래스를 상속받는 클래스들에 많은 변경사항이 발생할 것이다.
◦
또한 Human 클래스의 메소드가 변경되는 경우, Override되지 않은 하위 클래스의 해당 메소드들은 모두 변경된다.
◦
이는 상속을 통해 강하게 결합되어있기 때문이다. 즉 상속은 하위 클래스에게 상위 클래스의 정보를 모두 알려줘버리기 때문에 문제가 발생한다.
조합이란?
•
조합이란 기존 클래스가 새로운 클래스의 구성요소로써 쓰이는 것이라고 생각하면 된다.
•
새로운 클래스를 만들고 private 필드로 기존 클래스의 인스턴스를 사용한다고 생각하면 된다.
◦
여기서 든 생각인데 원시값을 wrapping하는 것과 비슷하다는 생각이 들었다.
◦
또한 다양한 디자인 패턴들에서의 구현 방법이 생각났다.
▪
Decorator에서 인터페이스를 상속받는 대신 wrapping하는 형태로 간다든지
•
Proxy도 마찬가지다.
•
위 클래스를 조합을 사용한다면 아마 다음과 같을 것이다.
public class Korean {
public Human human;
public Korean(String name) {
this.human = Human.of(name);
}
public introduceSelf() {
human.introduceSelf();
}
}
Java
복사
•
책임이 완벽하게 분리되었다. 다만 바꿔끼우면 될뿐이다.
•
상속의 문제점에서 벗어날 수 있다.
그렇다면 언제 상속을 쓰고 조합을 쓸까?
•
상속을 사용하기 적합한 상황은 다음과 같다.
◦
두 클래스 간의 관계에 대해서 확장을 고려하고 충분한 고민끝에 내린 결론이 is-a일 때
◦
두 클래스 중 상위 클래스에서 발생할 수 있는 변경사항이 하위 클래스까지 전파되어도 괜찮을 때
•
상속을 사용했을 때, 코드 재사용으로 얻어지는 이점만 생각하지 말고 확장이 발생하므로 내가 작성하는 extends의 클래스에 영향을 받을수 있다는 것을 생각하자.
•
조합을 사용하기 적절한 상황은 다음과 같다.
◦
두 클래스 간의 관계가 has-a일 때
•
즉 두 클래스 간의 관계가 동등하지 않고 어느 한쪽의 구성요소가 된다면 조합을 사용하는 것이 적절하다.