리팩토링 39. 슈퍼클래스를 위임으로 바꾸기
- etc/리팩토링
- 2023. 5. 10.
들어가기 전
이 글은 인프런 백기선님의 강의를 복습하며 작성한 글입니다.
리팩토링 39. 슈퍼클래스를 위임으로 바꾸기 (Replace Superclass with Delegate)
- 객체지향에서 '상속'은 기존의 기능을 재사용하는 쉬우면서 강력한 방법이지만, 때로는 적절하지 않은 경우도 있다.
- 서브클래스는 슈퍼클래스의 모든 기능을 지원해야 한다.
- Stack이라는 자료구조를 만들 때, List를 상속받는 것이 좋을까?
- 다 지원하는 것은 맞지 않다. 따라서 Stack / List는 상속 구조에 어울리지 않음.
- 서브 클래스는 슈퍼클래스 자리를 대체하더라도 잘 동작해야 한다.
- 리스코프 치환 원칙
- 서브클래스는 슈퍼클래스의 변경에 취약하다.
- 서브클래스는 슈퍼클래스의 모든 기능을 지원해야 한다.
- 그렇다면 상속을 사용하지 않는 것이 좋은가?
- 상속은 적절한 경우에 사용하다면 매우 쉽고 효율적인 방법이다.
- 따라서, 우선 상속을 적용한 이후에 적절하지 않다고 생각되면, 그 때 이 리팩토링을 적용하자.
- 이 리팩토링은 '상속 구조'를 '위임으로 제거'하는 리팩토링이다.
자바의 상속에는 다음 제약이 있다.
- 오로지 하나만 상속 가능함.
- 슈퍼 클래스의 기능을 모두 서브 클래스에서 제공해야한다.
- 리스코프 치환원칙을 고려해야함. (슈퍼 클래스를 서브 클래스로 바꿔도, 잘 동작해야한다.)
이 제약을 고려해서 두 클래스의 관계가 '상속 관계'가 되는 것이 맞는지를 생각해야한다. 예를 들어 Stack / List가 '상속구조'에 타당한지를 고려해보자.
- Stack(서브 클래스)는 List(슈퍼 클래스)의 모든 기능(모든 메서드)를 구현해야 할까?
Stack은 List의 모든 기능을 구현할 필요는 없다. 따라서 상속 관계를 가지는 것은 적절하지 않다. 대신에 Stack이 List를 내부 필드로 가지고 이용하는 형태로 구현하면 좋을 것이다.
리스코프 치환 원칙을 고려하는 것도 중요하다. 예를 들어 부모 클래스에 rotate 기능이 있다고 가정해보자. 부모 클래스가 90도 회전하는 것인데, 부모 클래스를 자식 클래스로 바꾸었다고 rotate 기능이 '색깔을 바꾸는 기능'으로 동작하면 안된다. 슈퍼 클래스가 가지고 있는 Operation은 일종의 규약이 되는 것이다.
리스코프 치환 원칙에 따른 서브 클래스는 '기능 자체가 특수한 형태'로 구현되어야 할 뿐이지, 기능 자체가 완전히 바뀌어서는 안된다고 한다.
그렇다면 상속을 사용하지 않는 것이 좋을까?
소프트웨어에는 항상 / 절대라는 말은 어울리지 않는다. 상속은 나쁜 것이 아니라 유용한 도구이다. 다만 필요할 때 적절히 사용해야 하는 것이다. 따라서 처음에는 상속을 먼저 적용해본다. 처음에는 적절해 보이는 상속일 수도 있는데, 나중에는 '나쁜 것'처럼 보일 때도 있다. 이 때는 리팩토링을 통해 '상속'을 '위임'으로 바꿔주면서 상속을 제거해주면 된다.
코드
아래에서 Scroll 클래스를 살펴보자. Scroll 클래스는 특정한 아이템이지 '카테고리'를 표현하지는 않는다. 이런 생각이 아니더라도 Scroll - CategoryItem의 상속 구조가 적절하지 않다고 생각해서 위임으로 변경할 수 있다. 여기서는 Scroll이 CategoryItem을 상속 받는 것이 아니라, 위임받도록 리팩토링 해보자.
public class Scroll extends CategoryItem {
private LocalDate dateLastCleaned;
public Scroll(Integer id, String title, List<String> tags, LocalDate dateLastCleaned) {
super(id, title, tags);
this.dateLastCleaned = dateLastCleaned;
}
public long daysSinceLastCleaning(LocalDate targetDate) {
return this.dateLastCleaned.until(targetDate, ChronoUnit.DAYS);
}
}
위 코드는 다음 순서로 리팩토링 할 수 있다.
- Scroll 클래스의 필드로 상위 타입(CategoryItem)을 선언한다.
- Scroll 생성자에서 CategoryItem 필드를 생성하도록 한다.
- Scroll에서 상속을 제거한다.
이렇게 하면 슈퍼클래스를 위임으로 제거(상속구조의 제거) 리팩토링이 완료된다.
// 상속 구조를 위임으로 제거했다. (슈퍼 클래스를 위임으로 제거하기)
public class Scroll{
private LocalDate dateLastCleaned;
private CategoryItem categoryItem;
public Scroll(Integer id, String title, List<String> tags, LocalDate dateLastCleaned) {
this.categoryItem = new CategoryItem(id, title, tags);
this.dateLastCleaned = dateLastCleaned;
}
public long daysSinceLastCleaning(LocalDate targetDate) {
return this.dateLastCleaned.until(targetDate, ChronoUnit.DAYS);
}
}
'etc > 리팩토링' 카테고리의 다른 글
냄새 19. 내부자 거래 (0) | 2023.05.10 |
---|---|
리팩토링 40. 서브클래스를 위임으로 바꾸기 (0) | 2023.05.10 |
리팩토링 38. 중재자 제거하기 (0) | 2023.05.10 |
냄새 18. 중재자 (0) | 2023.05.10 |
리팩토링 37. 위임 숨기기 (0) | 2023.05.10 |