[Refactoring] 냄새 11. 기본형 집착 (2)
05 Jan 2023해당 포스트는 inflearn의 백기선님의 강의인 리팩토링 을 듣고 정리한 글입니다.
냄새 11. 기본형 집착 (2)
리팩토링 32. 조건부 로직을 다형성으로 바꾸기
- 복잡한 조건식을 상속과 다형성을 사용해 코드를 보다 명확하게 분리할 수 있다.
- swich 문을 사용해서 타입에 따라 각기 다른 로직을 사용하는 코드.
- 기본 동작과 (타입에 따른) 특수한 기능이 섞여있는 경우에 상속 구조를 만들어서 기본 동작을 상위 클래스에 두고 특수한 기능을 하위클래스로 옮겨서 각 타입에 따른 “차이점”을 강조할 수 있다.
- 모든 조건문을 다형성으로 옮겨야 하는가? 단순한 조건문은 그대로 두어도 좋다. 오직 복잡한 조건문을 다형성을 활용해 좀 더 나은 코드로 만들 수 있는 경우에만 적용한다. (과용을 조심하자.)
아래의 ‘Employee’ 클래스는 각 직원의 타입별, 휴게 시간과 접근 권한을 가져오는 로직이다.
- Employee
public class Employee {
private String type;
private List<String> availableProjects;
public Employee(String type, List<String> availableProjects) {
this.type = type;
this.availableProjects = availableProjects;
}
public int vacationHours() {
return switch (type) {
case "full-time" -> 120;
case "part-time" -> 80;
case "temporal" -> 32;
default -> 0;
};
}
public boolean canAccessTo(String project) {
return switch (type) {
case "full-time" -> true;
case "part-time", "temporal" -> this.availableProjects.contains(project);
default -> false;
};
}
}
먼저 테스트 코드를 실행해보자. 각 직원 구분에 따른 권한 및 시간이 옳게 반환되는지 검증한다.
class EmployeeTest {
@Test
void fulltime() {
Employee employee = new Employee("full-time", List.of("spring", "jpa"));
assertEquals(120, employee.vacationHours());
assertTrue(employee.canAccessTo("new project"));
assertTrue(employee.canAccessTo("spring"));
}
@Test
void partime() {
Employee employee = new Employee("part-time", List.of("spring", "jpa"));
assertEquals(80, employee.vacationHours());
assertFalse(employee.canAccessTo("new project"));
assertTrue(employee.canAccessTo("spring"));
}
@Test
void temporal() {
Employee employee = new Employee("temporal", List.of("jpa"));
assertEquals(32, employee.vacationHours());
assertFalse(employee.canAccessTo("new project"));
assertFalse(employee.canAccessTo("spring"));
assertTrue(employee.canAccessTo("jpa"));
}
}
성공하는 것을 확인 할 수 있다.
이제 switch로 작동하는 로직을 각각 다형성을 이용해 클래스로 분리하고 Employee를 상속받아 보도록 하자
먼저 접근제어자를 protected 로 바꾸어 외부에서 호출할 수 없도록 한다. 이후 생성자를 통해 필요한 데이터를 부모객체에서 가져올수 있도록 추가해주자.
protected String type;
protected List<String> availableProjects;
public Employee(String type, List<String> availableProjects) {
this.type = type;
this.availableProjects = availableProjects;
}
public Employee(List<String> availableProjects) {
this.availableProjects = availableProjects;
}
public Employee() {
}
그리고 switch에 해당하는 FULL-TIME, PART-TIME, TEMPORAL의 타입을 각각 객체로 만들어 준다.
먼저 Employee를 상속받고, vacationHours
, canAccessTo
두 메소드를 @Override 하여 switch의 조건과 동일하게 반환하도록 구현해준다.
public class FullTimeEmployee extends Employee{
@Override
public int vacationHours() {
return 120;
}
@Override
public boolean canAccessTo(String project) {
return true;
}
}
public class PartTimeEmployee extends Employee{
public PartTimeEmployee(List<String> availableProjects) {
super(availableProjects);
}
@Override
public int vacationHours() {
return 80;
}
}
public class TemporalEmployee extends Employee{
public TemporalEmployee(List<String> availableProjects) {
super(availableProjects);
}
@Override
public int vacationHours() {
return 32;
}
}
이후 ‘Employee’ 에서는 조건을 지워주고, 기본값을 반환하도록 수정한다.
vacationHours
메소드는 추상화를 해주어 자식객체에서 구현해주도록 변환한다.
public abstract class Employee {
protected String type;
protected List<String> availableProjects;
public Employee(String type, List<String> availableProjects) {
this.type = type;
this.availableProjects = availableProjects;
}
public Employee(List<String> availableProjects) {
this.availableProjects = availableProjects;
}
public Employee() {
}
public abstract int vacationHours();
public boolean canAccessTo(String project) {
return availableProjects.contains(project);
}
}
Employee
에서만 데이터를 가져오는 테스트 코드도 수정해주도록 하자.
그렇게 된다면 호출부에서는 생성자로 타입을 넘겨줄 필요없이 알아서, 데이터를 검증해 반환해준다.
class EmployeeTest {
@Test
void fulltime() {
Employee employee = new FullTimeEmployee();
assertEquals(120, employee.vacationHours());
assertTrue(employee.canAccessTo("new project"));
assertTrue(employee.canAccessTo("spring"));
}
@Test
void partime() {
Employee employee = new PartTimeEmployee(List.of("spring", "jpa"));
assertEquals(80, employee.vacationHours());
assertFalse(employee.canAccessTo("new project"));
assertTrue(employee.canAccessTo("spring"));
}
@Test
void temporal() {
Employee employee = new TemporalEmployee(List.of("jpa"));
assertEquals(32, employee.vacationHours());
assertFalse(employee.canAccessTo("new project"));
assertFalse(employee.canAccessTo("spring"));
assertTrue(employee.canAccessTo("jpa"));
}
}
테스트 코드 또한 정상작동하는 것을 확인 할 수있다.