[Refactoring] 냄새 20. 거대한 클래스
10 Jan 2023해당 포스트는 inflearn의 백기선님의 강의인 리팩토링 을 듣고 정리한 글입니다.
냄새 20. 거대한 클래스
- 어떤 클래스가 너무 많은 일을 하다보면 필드도 많아지고 중복 코드도 보이기 시작한다.
- 클라이언트가 해당 클래스가 제공하는 기능 중에 일부만 사용한다면 각각의 세부기능을 별도의 클래스로 분리할 수 있다.
- “클래스 추출하기 (Extract Class)”를 사용해 관련있는 필드를 한곳으로 모을 수 있다.
- 상속 구조를 만들 수 있다면 “슈퍼클래스 추출하기 (Extract Superclass)” 또는 “타입 코드를 서브클래스로 교체하기”를 적용할 수 있다.
- 클래스 내부에 산재하는 중복 코드는 메소드를 추출하여 제거할 수 있다.
리팩토링 41. 슈퍼클래스 추출하기
- 두개의 클래스에서 비슷한 것들이 보인다면 상속을 적용하고, 슈퍼클래스로 “필드 올리기 (Pull Up Field)”와 “메소드 올리기 (Pull Up Method)”를 사용한다.
- 대안으로는 “클래스 추출하기 (Extract Class)”를 적용해 위임을 사용할 수 있다.
- 우선은 간단히 상속을 적용한 이후, 나중에 필요하다면 “슈퍼클래스를 위임으로 교체하기”를 적용한다.
아래의 예시코드는 직원과 부서를 클래스로 만들어서 구현한 객체이다.
- Department
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();
}
}
- Employee
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;
}
}
각각 Department
와 Employee
를 살펴보면 이름이나 월, 연간 비용 등 서로 중복되는 항목들이 보인다.
이러한 중복되는 필드들을 슈퍼클래스로 빼서 묶어줄 수 있다.
그렇다고 무작정 옮겨줄 수만은 없다. Department
와 Employee
에서 이름 과 연관 비용(annualCost)을 가져오는 로직은 동일하지만 월별 비용에 대한 비지니스 로직이 다르기 때문에 이를 고려해서 리팩토링을 진행해 줘야한다.
먼저 상위 클래스인 Party
클래스를 생성하고 동일한 로직은 이름에 대한 필드와, 연간비용 로직에 메소드를 옮겨주도록 하자.
여기서 그냥 옮겨주지 말고, 각각 Department
와 Employee
클래스에서 명명한 메소드 명이 다르기 때문에 annualCost
로 이름을 변경해서 옮겨준다.
public class Party {
private String name;
public Party(String name) {
this.name = name;
}
public String getName() {
return name;
}
public double annualCost() {
return this.monthlyCost() * 12;
}
}
이후, Department
와 Employee
클래스는 각각 party
를 상속하고, 생성자를 통해 부모클래스로 name 값을 전달하도록 한다.
public class Employee extends Party {
...
public Employee(String name) {
super(name);
}
...
}
public class Department extends Party {
...
public Department(String name) {
super(name);
}
...
}
그렇다면 구현체가 다른 monthlyCost()
메소드는 어떻게 하면 될까?
간단하다. 해당 메소드를 상위 클래스인 Party
에서 추상화 시키고 하위 클래스에서 구현을 해주면 된다.
이렇게 하면 클래스가 너무 커질경우 분리해주어 중복되는 로직을 하나로 합칠 수 있고, 각각 필요한 기능들만 분리가 수월해진다.
public abstract class Party {
...
protected abstract double monthlyCost();
}
public class Employee extends Party {
...
@Override
public double monthlyCost() {
return monthlyCost;
}
}
public class Department extends Party {
...
@Override
public double monthlyCost() {
return this.staff.stream().mapToDouble(e -> e.monthlyCost()).sum();
}
}