리팩토링 41. 슈퍼클래스 추출하기
- etc/리팩토링
- 2023. 5. 10.
들어가기 전
이 글은 인프런 백기선님의 강의를 복습하며 작성한 글입니다.
리팩토링 41. 슈퍼클래스 추출하기(Extract Superclass)
- 슈퍼 클래스 추출하기
- 두 개 이상의 거대한 클래스가 있고, 상속 구조를 만들 수 있다면 슈퍼 클래스를 추출하면서 클래스의 크기를 줄일 수 있음.
- 방법
- 두 개의 클래스에서 비슷한 것들이 보인다면 상속을 적용하고, 슈퍼클래스로 'Pull Up Field' / 'Pull Up Method'를 사용한다.
- 슈퍼 클래스의 대안으로는 '클래스 추출하기'를 적용해 '위임'을 사용할 수도 있다.
- 접근
- 먼저 간단히 상속을 적용한다.
- 만약 상속 구조가 적절하지 않거나 필요하다면 '슈퍼클래스를 위임으로 교체하기'를 적용한다.
각 클래스에 공통이 되는 부분이 많고, 클래스 자체가 꽤 큰 편이라면 슈퍼 클래스로 만들어서 공통되는 부분을 재사용하는게 유익할 수 있다. 이 리팩토링을 통해서 거대한 클래스를 작게 만들어 줄 수 있기 때문이다. 뿐만 아니라 다른 접근 방식으로는 '클래스 추출하기 + 위임'을 적용해서 코드를 줄일 수도 있다.
슈퍼클래스로 추출하는 것이 간단하기 때문에 아래와 같이 접근하는 것이 좋다.
- 상속 구조를 만들어서 코드 줄이기
- 적절하지 않다고 판단되면, 슈퍼 클래스를 위임으로 교체하기를 적용하면 됨.
코드
이 코드는 슈퍼 클래스 추출하기의 예시 코드다.
Deparment / Employee 클래스는 서로 다른 일을 하는 클래스지만 중복된 필드, 비슷한 메서드가 일부 존재하기 때문에 하나의 슈퍼 클래스로 추출해서 중복되는 코드를 제거할 수 있다. (슈퍼 클래스가 적절한지 판단하는 것은 그 이후의 문제이다). 이 예시에서는 Deparment / Employee 클래스를 통해 Party라는 슈퍼 클래스를 추출하고자 한다.
// Department, Employee 비슷한 코드가 있다. 따라서 슈퍼클래스로 추출하기를 통해 중복 코드 제거 가능.
// 1. 월간 비용 + 연간 비용 지불
// 2. 이름
public class Department {
private String name;
private List<Employee> staff;
public String getName() {return name;}
public List<Employee> getStaff() {return staff;}
public double totalMonthlyCost() {return this.staff.stream().mapToDouble(e -> e.getMonthlyCost()).sum();}
public double totalAnnualCost() {return this.totalMonthlyCost() * 12;}
public int headCount() {return this.staff.size();}
}
public class Employee {
private Integer id;
private String name;
private double monthlyCost;
public double annualCost() {return this.monthlyCost * 12;}
public Integer getId() {return id;}
public String getName() {return name;}
public double getMonthlyCost() {return monthlyCost;}
}
다음 순서대로 리팩토링을 진행한다.
- Party 클래스를 만든다.
- Employee에 있는 필드 / 메서드들 중 다음을 Party 클래스로 이동시킨다. 이름이 비슷하거나 비슷한 행동을 하는 녀석들이기 때문이다.
- name()
- annualCost()
- monthlyCost()
- 메서드 이름이 달랐기 때문에 하나로 통일시켜준다. (annualCost, monthlyCost)
- 살펴보니 monthlyCost()는 Employee / Department에서 각각 다르게 동작한다. 따라서 monthlyCost()는 추상 메서드로 만들고, 하위 클래스에서 구현하도록 한다.
- Party에 추상 메서드가 생겼기 때문에 Party는 추상 클래스가 된다.
- 하위 클래스에서 name, annualCost()는 제거한다.
이 방식대로 리팩토링을 진행하면 결과는 다음과 같다.
// MonthlyCost는 클래스마다 구하는 방법이 다르기 때문에 추상 메서드로 변경
// 그리고 하위에서 재정의한다.
public abstract class Party {
protected String name;
protected double monthlyCost;
public double annualCost() {return this.monthlyCost * 12;}
public abstract double getMonthlyCost();
}
public class Employee extends Party {
private Integer id;
public Integer getId() {return id;}
public String getName() {return name;}
@Override
public double getMonthlyCost() {return super.monthlyCost;}
}
// Department, Employee 비슷한 코드가 있다. 따라서 슈퍼클래스로 추출하기를 통해 중복 코드 제거 가능.
// 1. 월간 비용 + 연간 비용 지불
// 2. 이름
public class Department extends Party{
private List<Employee> staff;
public List<Employee> getStaff() {return staff;}
public int headCount() {return this.staff.size();}
@Override
public double getMonthlyCost() {return this.staff.stream().mapToDouble(e -> e.getMonthlyCost()).sum();}
}
'etc > 리팩토링' 카테고리의 다른 글
냄새 22. 데이터 클래스 (0) | 2023.05.10 |
---|---|
냄새 21. 서로 다른 인터페이스의 대안 클래스들 (0) | 2023.05.10 |
냄새 20. 거대한 클래스 (0) | 2023.05.10 |
냄새 19. 내부자 거래 (0) | 2023.05.10 |
리팩토링 40. 서브클래스를 위임으로 바꾸기 (0) | 2023.05.10 |