호호 스터디에서 Chapter 4: 재사용: 상속보단 조립을 발표하기 전에, 미리 책을 읽고 공부한 내용을 기록한 포스팅이다.
이펙티브 자바 (아이템 18. 상속보다는 컴포지션을 사용하라) 내용과도 유사한 점이 존재한다.
상속을 통한 재사용의 단점
지난 포스팅에서 `상속이 정말 좋을까?` 라는 소주제로 글을 쓴 적이 있다.
https://kth990303.tistory.com/280
상속을 통해 코드를 재사용하고 중복을 줄일 수 있다는 장점이 있지만,
상속은 변경의 유연함이라는 측면에서 치명적인 단점들을 몇 가지 가진다.
첫째로, 상위 클래스 변경을 어렵게 만든다.
상속을 받는 자식 클래스는, 부모 클래스의 메서드를 의존하며, 거기에 추가적인 구현을 하는 메서드를 가지기 때문에
상위 클래스를 의존한다.
따라서 상위 클래스가 변경되면, 이 클래스를 상속받는 모든 클래스들의 변경이 불가피하게 된다.
위 그림처럼 상위 클래스를 변경하면, 그 클래스의 메서드들을 오버라이딩하는 하위 클래스들은 당연히 영향을 받을 수밖에 없다.
둘째로, 클래스의 불필요한 증가가 발생한다.
만약, 상위 클래스의 기능을 상속받는 클래스이면서, 다른 하위 클래스들과 소통해야되는 클래스를 만들어야 한다면?
그 클래스 개수만큼 클래스 수가 증가하게 된다.
예를 들어, 그래프 알고리즘 클래스가 존재하며, 이를 상속받는 Dijkstra 클래스와 DFS 클래스가 존재한다고 해보자.
그런데, 어떤 개발자가 dijkstra를 써서 최단거리를 구한 후, 그 거리들의 정보끼리 dfs를 이용해야 하는 로직을 만들어야 한다고 하면, Dijkstra 클래스와 Dfs 클래스는 서로 다른 클래스이기 때문에 DijkstraAndDfs 클래스를 따로 만들어주지 않는 이상 두 클래스 내 메서드를 함께 사용할 수 없다.
동시에 사용하고 싶다면 위처럼 클래스를 새로 만들거나, 아니면 인스턴스 변수로 Dijkstra, Dfs 타입의 변수를 가지고 있어야 한다. (이 방법이 아래에 후술할 '조립(Composition)'이다.)
셋째로, 상속의 오용으로 인한 혼란을 야기할 수 있다.
상속받은 클래스는 부모 클래스의 메서드를 오버라이딩하며, 부모 클래스 내에 존재하는 메서드까지 사용할 수 있다.
따라서 자식 클래스에서 의도한 메서드뿐만 아니라, 상위 클래스의 메서드들도 사용할 수 있으며,
이로 인한 혼란으로 잘못된 로직을 작성할 가능성이 높아진다.
상속은 언제 사용할까?
위와 같은 문제점때문에 상속은 항상 조심히 신경써서 사용해야 한다.
단순히 공통 로직이 존재한다고 해서, 재사용 목적으로 사용하기 보단, 기능 확장 관점에서 바라보며 is-a 관계일 때만 사용해야 한다.
Phone 클래스와 Computer 클래스에서 '화면이 켜진다'는 공통점이 있다고 TurnOn 클래스를 상속받으면 안되는 것처럼, 단순 재사용 목적으로 사용하는 것은 금물이다.
명확한 is-a 관계에서 상위클래스의 기능을 확장해 나가면서 상속을 사용하고,
클래스 개수가 너무 많아지거나 변경이 어려워질 경우 조립(Composition)으로 전환하는 것도 고려해봐야 한다.
조립을 이용한 재사용
그렇다면 조립(Composition)은 무엇일까?
조립은 필드에서 다른 객체를 참조하는 방식으로 구현된다.
즉, 인스턴스 변수로 상태를 가지는 것을 의미한다.
조립을 이용하면, 불필요한 클래스를 증가시킬 필요 없이,
상태로 가진 변수의 메서드들을 적절히 조합하거나 이용하면 된다.
만약 다형성을 활용하고 싶다면 인터페이스를 이용하여 조립을 활용해보는 것을 고려해보자.
인터페이스는 메서드 선언부만 가지고 있기 때문에 부모클래스를 크게 의존할 필요 없이 다형성을 활용하여 변경의 유연함을 챙길 수 있고, 캡슐화와 추상화 또한 지킬 수 있다.
인터페이스를 이용하면 다형성 활용이 가능해 mock test도 가능하다.
랜덤값을 반환하는 테스트는 상당히 만들기 힘든데,
인터페이스에 선언된 메서드를 재정의해서 CustomGenerator를 만들어줄 수 있다.
물론 조립이 장점만 존재하는 것은 아니다.
인스턴스 변수를 갖고 있다는 것 자체가, 그 객체에게 의존하기 위해 상태를 갖고 있다는 것이기 때문에 변경의 유연성을 떨어뜨릴 수 있다.
아래 포스팅에서 '상태를 줄일수록 객체의 책임이 적어지고 클린해진다'는 부분을 참고하면 좋을 듯하다 :)
https://kth990303.tistory.com/282
사실 이번 chapter 4 포스팅은 Chapter 3의 연장선이라 많은 내용이 담겨져있지 않다.
어쩌면 Chapter 3 포스팅 내용과 비슷할 수도 있다.
이번 포스팅에선 무분별한 상속 남용의 주의점을 확실하게 꼬집는 포스팅으로 생각하면 좋을 듯하다 :)
상속을 제대로 알고 사용한다면, 더더욱 클린코드를 짜는 데에 도움되지 않을까 ㅎㅎ
'JAVA > JAVA | Spring 학습기록' 카테고리의 다른 글
[JAVA] VO(Value Object)로 원시값을 포장해보자 (2) | 2022.03.16 |
---|---|
[JAVA] Cache를 이용한 재사용으로 성능을 높이자 (0) | 2022.03.16 |
Collections.EMPTY_LIST vs Collections.emptyList() 무엇이 다를까? (6) | 2022.03.03 |
[호호 스터디] 다형성과 추상 타입_ 객체지향과 디자인 패턴 Chapter 3 (2) | 2022.03.02 |
null을 반환하지 말고 빈 객체, 또는 Optional을 반환하자 (0) | 2022.03.01 |