[리팩토링] 악취 11 : 기본형 집착 (2)

 

✍️ 악취 11 : 기본형 집착 (2)

표현하려는 데이터가 심플하다면 언어가 제공하는 기본 타입으로도 충분히 구현할 수 있지만, 표현할 데이터 구조가 복잡해지고 제공할 기능이 복잡해진다면 단순 기본형으로 원활한 기능을 구현하기 어렵다. 가령 단순 화씨온도를 표현하고 싶다면 실수형으로 가능하겠지만 화씨, 섭씨 등 다양한 온도를 표현하고 싶다면 별도의 클래스로 제공하는 것이 더 합리적일 것이다.

 

여기 기본형 집착 악취를 해결하기 위한 두 가지 리팩토링 기법이 있다.

1. "기본형을 객체로 바꾸기" 

2. "조건부 로직을 다형성으로 바꾸기"

 

🍊 조건부 로직을 다형성으로 바꾸기

비슷하지만 서로 다른 것을 표현하는 경우 문자열, 열거형, 숫자 등을 사용하기도 한다. 가령 고객의 등급을 표현할 때 열거형을 사용하는 것이 한 예시이다.

public enum Grade {
    SILVER, GOLD, VIP
}

 

이렇게 비슷하지만 다른 것을 표현하는 변수들은 종종 if문에 사용되어 서로 다른 로직을 처리하는 분기문의 플래그로 사용되기도 한다. 만약 플래그 변수가 분기문에서 자주 사용되고 동시에 분기마다 서로 다른 로직을 수행하고 경우에 따라 특정 타입에만 유효한 필드가 있다면 본 리팩토링을 고려해 볼 수 있다.

 

아래 클래스와 같이 type으로 서로 다른 것을 표현하고, type을 보고 서로 다른 로직을 수행하기보단, type에 따른 별도의 서브 클래스 혹은 구현체를 정의해서 코드를 분리하는 것이 더 효율적이다.

@AllArgsConstructor
public class Employee {

    private String type;

    private List<String> availableProjects;

    public int vacationHours() {
        switch (type) {
            case "full-time":
                return 120;
            case "part-time":
                return 80;
            case "temporal":
                return 32;
            default:
                throw new IllegalArgumentException();
        }
    }

    public boolean canAccessTo(String project) {
        switch (type) {
            case "full-time":
                return true;
                
            case "part-time":
            case "temporal":
                return availableProjects.contains(project);
            default:
                throw new IllegalArgumentException();
        }
    }
}

 

Employee를 추상 클래스로 변경하고 속성에 따라 별도의 서브 클래스를 정의해서 각각의 표현과 로직을 분리하는 것이 좋다. 

@AllArgsConstructor
@NoArgsConstructor
public abstract class Employee {

    protected List<String> availableProjects;

    public abstract int vacationHours();

    public boolean canAccessTo(String project){
        return availableProjects.contains(project);
    }
}
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;
    }

}