독서에서 한걸음

타입 코드를 서브클래스로 변경하기

oncerun 2022. 12. 19. 22:10
반응형

비슷하지만 다른 것들을 표현해야 하는 경우, 나는 열거형을 많이 사용한다. 

 

이를 통해 타입 검증을 하거나 필요한 정보를 저장하고 있다가 제공해주거나 해당 값에 대한 기능을 제공하는 메서드를 포함할 수 있기 때문이다. 

 

분기에 대한 생각 없이 Enum을 사용하다보면 매우 좋고 한계가 없어 보인다. 

 

하지만 최근 각각의 타입에 맞는 기능을 추가 구현해야하는 요청이 있는데, 이 경우 if문이나 switch문을 사용해

타입마다 해야하는 로직을 작성하는 경우가 많아졌다. 

 

이는 보기도 좋지 않고 타입이 추가될 때마다 해당 코드를 찾아서 고쳐야 하는 상황.. 즉 변경점이 퍼져있어 하나의 코드를 고쳤지만 여러 모듈에 영향을 받는 상황이 됐다. 

 

이러한 상황에 적용할 수 있는 리팩토링을 찾아보았다.

 

Replace Type Code with Subclasses

 

조건문을 다형성으로 표현할 수 있을 때, 서브클래스를 만들고 조건부 로직을 다형성으로 바꾸자.

 

특정 타입에만 유효한 필드가 있을 수 있다. 

 

이는 enum을 사용하게 되면 특정 타입만 필드가 필요한 경우가 있다. 이 경우 다른 필드는 빈 문자열을 갖거나 무의미한 값 (0, 1)과 같이 기본값을 설정해야 한다. 

 

이런 경우 서브 클래스를 만들고 필드 내리기를 사용할 수 있다.

 

 

우선 상속이 가능한 경우를 살펴보자.

 

public class Employee {

    private String name;

    private String type;

    public Employee(String name, String type) {
        this.validate(type);
        this.name = name;
        this.type = type;
    }

    private void validate(String type) {
        List<String> legalTypes = List.of("engineer", "manager", "salesman");
        if (!legalTypes.contains(type)) {
            throw new IllegalArgumentException(type);
        }
    }

    public String getType() {
        return type;
    }

    @Override
    public String toString() {
        return "Employee{" +
                "name='" + name + '\'' +
                ", type='" + type + '\'' +
                '}';
    }
}

여기엔 3가지 타입이 존재한다 enginner, manager, salesman 이들을 각각의 서브클래스로 만들어 보자.

 

public class Engineer extends Employee{

    public Engineer(String name, String type) {
        super(name, type);
    }

    @Override
    public String getType() {
        return "engineer";
    }
}

facotry method를 부모 클래스에 만들어 주었다.

 

//factory method
public static Employee createEmployee(String name, String type) {
    return switch (type) {
        case "engineer" -> new Engineer(name);
        case "manager" -> new Manager(name);
        case "salesman" -> new SalesMan(name);
        default -> throw new IllegalArgumentException(type);
    };
}

 

이후 기존 타입을 가져오는 것을 서브클래스에서 책임지도록 하고 코드를 변경해주었다.

 

public abstract class Employee {

    private String name;

    private String type;

    protected Employee(String name) {
        this.name = name;
    }

    //factory method
    public static Employee createEmployee(String name, String type) {
        return switch (type) {
            case "engineer" -> new Engineer(name);
            case "manager" -> new Manager(name);
            case "salesman" -> new SalesMan(name);
            default -> throw new IllegalArgumentException(type);
        };
    }
    protected abstract String getType();

    @Override
    public String toString() {
        return "Employee{" +
                "name='" + name + '\'' +
                ", type='" + getType() + '\'' +
                '}';
    }
}

 

만약 이미 상속구조를 가지고 있다면 어떻게 할 것인가?

 

기본 타입을 감싸는 클래스를 만들고

public class EmployeeType {
}

 

해당 클래스를 각각의 타입이 상속받도록 한다.

 

public class Manager extends EmployeeType{}

public class Salesman extends EmployeeType{}

public class Engineer extends EmployeeType{}
public class Employee {

    private String name;

    private EmployeeType type;

    public Employee(String name, String type) {
        this.name = name;
        this.type = this.employeeType(type);
    }
    public EmployeeType employeeType(String type) {
        return switch (type) {
            case "engineer" -> new Engineer();
            case "manager" -> new Manager();
            case "salesman" -> new Salesman();
            default -> throw new IllegalArgumentException(type);
        };
    }

    public String capitalizedType() {
        return this.type.capitalizedType();
    }

    @Override
    public String toString() {
        return "Employee{" +
                "name='" + name + '\'' +
                ", type='" + type.toString() + '\'' +
                '}';
    }
}

 

간접적으로 상속을 활용한 타입을 서브 클래스로 만들 수 있다. 

반응형