비슷하지만 다른 것들을 표현해야 하는 경우, 나는 열거형을 많이 사용한다.
이를 통해 타입 검증을 하거나 필요한 정보를 저장하고 있다가 제공해주거나 해당 값에 대한 기능을 제공하는 메서드를 포함할 수 있기 때문이다.
분기에 대한 생각 없이 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() + '\'' +
'}';
}
}
간접적으로 상속을 활용한 타입을 서브 클래스로 만들 수 있다.
'독서에서 한걸음' 카테고리의 다른 글
Repeated Switches (0) | 2022.12.22 |
---|---|
Replace Conditional with Polymorphism (0) | 2022.12.21 |
Primitive Obsession (0) | 2022.12.19 |
Shotgun Surgery (0) | 2022.12.18 |
Divergent Change (0) | 2022.12.14 |
댓글