본문 바로가기
책/이펙티브 코틀린

2부 코드설계 > 6장 클래스 설계 > 상속보다는 컴포지션을 사용하라

by 정선한 2022. 9. 1.
728x90
반응형

2부 코드 설계 > 6장 클래스 설계 > 상속보다는 컴포지션을 사용하라

오늘의 TIL 3줄 요약
  • 명확한 is-a 관계에서는 상속 (계층구조의 표현, 뷰를 출력)
  • 컴포지션을 통한 인터페이스 분리원칙, 리스코프 치환원칙 개선
  • 위임패턴과 포워딩 메서드 : 캡슐화가 깨지지 않도록 관리

1. 책에서 기억하고 싶은 내용

상속에 대한 몇 가지 단점
- 상속은 하나의 클래스만을 대상으로 할 수 있다. 상속을 사용하다 보면 많은 함수를 갖는 거대한 클래스를 가지게 되고 이는 복잡한 계층구조를 만들어 낸다.
- 상속은 클래스의 모든 것을 가져온다. 따라서 불필요한 함수를 가지는 클래스가 만들어질 수 있다. (인터페이스 분리 원칙 위반)
- 상속은 이해하기 어렵고, 메서드의 작동방식을 이해하기 위하여 슈퍼클래스를 여러 번 확인해야 하는 번거로움이 있다.
이러한 상속의 단점으로 Composition:컴포지션을 사용하는 것을 제시한다.

단, 컴포지션은 사용 시 객체를 다른 모든 객체에서 가지고 활용하는 추가코드가 필요하다.(이 부분은 예시로 작성된 코드를 참고하는 것이 필요) 이 때문에 상속을 선호하는 경우도 많다. 하지만 이러한 추가 코드로 인하여 코드를 읽는 사람들이 코드의 실행을 더 명확하게 예측할 수 있으며 해당 객체를 훨씬 유연하게 사용할 수 있다.

상속은 객체의 계층구조를 나타낼 때 굉장히 좋은 도구이지만 슈퍼클래스의 메서드, 제약, 행위 등의 모든 것을 가져오기 때문에 일부분을 재사용하기 위한 목적으로는 적합하지 않다.
따라서 상속받은 대상이 필요로 하지 않는 메서드를 가질 수 있기 때문에 인터페이스 분리 원칙에 위반되며 슈퍼클래스의 동작을 서브클래스에서 깨버리므로 리스코프 치환 원칙에도 위반된다.
이런 상황일 때, 컴포지션을 사용하면 위와 같은 설계문제가 발생하지 않는다. 하지만 타입의 계층 구조를 표현해야 하는 상황에서는 인터페이스를 활용한 다중 상속이 더 유리한 경우가 있으므로 상황에 따른 적절한 선택이 필요하다.

상속은 내부적인 구현 방법의 변경에 의하여 클래스의 캡슐화가 깨어질 수 있다. 라이브러리에 의존적인 상황의 경우에 라이브러리 변경 사항에 따라서 의도한 대로 코드가 작동하지 않을 수 있기 때문에 컴포지션을 사용해 주는 것이 필요하다.
다만 컴포지션을 사용하면 다형성이 사라지기 때문에 다형성을 유지하기 위하여 상속을 사용하는 경우에는 위임(delegation)패턴을 사용할 수 있다.

위임(delegation) 패턴은 클래스가 인터페이스를 상속받게 하고, 포함한 객체의 메서드를 활용해서, 인터페이스에서 정의한 메서드를 구현하는 패턴으로 이렇게 구현된 메서드를 포워딩 메서드(forwarding method)라고 부른다.
포워딩 메서드를 사용하여 작성하면 컴파일 시점에 해당 메서드들이 자동으로 만들어진다.

컴포지션과 상속의 차이
- 컴포지션은 더 안전하다. 외부에서 관찰되는 동작에만 의존한다.
- 컴포지션은 더 유연하다. 컴포지션은 여러 클래스를 대상으로 할 수 있으며 필요한 것만 받아서 사용할 수 있다.
- 컴포지션은 더 명시적이다. 장점이자 단점. 
- 컴포지션은 생각보다 번거롭다. 대상 클래스에 기능 추가 시 이를 포함하는 객체의 코드를 수정해야 한다.
- 상속은 다형성을 활용할 수 있다. 양날의 검.

상속은 언제사용하는 것이 좋은가? 
- "is-a"관계가 명확할 때, 슈퍼클래스를 상속하는 모든 서브클래스는 슈퍼클래스로도 동작할 수 있어야 하며 슈퍼클래스의 모든 단위 테스트는 서브클래스로도 통과할 수 있어야 한다. (리스 코프 치환 원칙)
- 객체 지향 프레임워크에서 뷰를 출력하기 위해 사용되는 Application(JavaFX), Activity(Android), UIViewController(iOS), React.Component(React) 등의 뷰를 출력하기 위해 사용하는 것에는 상속을 사용하는 것이 좋다는 것을 기억하라.

2. 오늘 읽은 소감, 떠오르는 생각에 대한 정리

항상 모든 기술서적을 읽을 때, 드는 생각은 "나는 참 잘못된 방식으로 살아왔구나." 입니다. 저는 Kotlin을 통한 실무 프로젝트 경험이 약 2년 정도인데, 그 시간 동안 정말 기능 추가에만 급급했구나 하는 생각이 들었습니다.

컴포지션, 들어는 봤고 일부 코드에 적용된 것도 보았지만 제가 적극적으로 코드에 반영했던 적은 없었던 것 같습니다. Java로 구현하면서 상속이 익숙했던 건지...는 잘 모르겠지만 매번 상속으로 구현하고 사용하지 않는 불필요한 코드에는 주석을 달아서 관리를 했었습니다. //TODO 이런 식으로요.🤯

이번 장을 통해 컴포지션이 왜 필요했는지, 왜 그 코드들은 컴포지션으로 구현했던 건지 좀 더 명확하게 알 수 있었고 앞으로의 코드에는 꼭 적용해서 사용해야겠다는 필요성을 느꼈습니다. 꼭꼭...꼭...컴포지션..적용...!

 

728x90
반응형