어떤 객체의 필드가 “특정한 경우에만” 값을 가진다는 것을 이해하는 것은 일반적으로 예상하지 못하기 때문에 이해하기 어렵다.
관련 리팩토링
클래스 추출하기 (Extract Class)를 사용해 해당 변수들을 옮길 수 있다.
함수 옮기기 (Move Function)을 사용해서 해당 변수를 사용하는 함수를 특정 클래스로 옮길 수 있 다.
특이 케이스 추가하기 (Introduce Special Case)를 적용해 “특정한 경우”에 해당하는 클래스를 만들어 해당 조건을 제거할 수 있다.
리팩토링 36. 특이 케이스 추가하기 (Introduce Special Case)
어떤 필드의 특정한 값에 따라 동일하게 동작하는 코드가 반복적으로 나타난다면, 해당 필드를 감싸는 “특별한 케이스”를 만들어 해당 조건을 표현할 수 있다.
이러한 매커니즘을 “특이 케이스 패턴”이라고 부르고 “Null Object 패턴”을 이러한 패턴(Introduce Special Case)의 특수한 형태라고 볼 수 있다.
예시
Before
customer.getName().equals("unknown")과 같은 special case 판단 유무가 반복되며 그에 따른 로직이 달라지는 것을 확인할 수 있다.
클래스 추출하기로 특정한 경우에 해당하는 클래스를 만들어 special case를 제거할 수 있다.
public class CustomerService {
public String customerName(Site site) {
Customer customer = site.getCustomer();
String customerName;
if (customer.getName().equals("unknown")) {
customerName = "occupant";
} else {
customerName = customer.getName();
}
return customerName;
}
public BillingPlan billingPlan(Site site) {
Customer customer = site.getCustomer();
return customer.getName().equals("unknown") ? new BasicBillingPlan() : customer.getBillingPlan();
}
public int weeksDelinquent(Site site) {
Customer customer = site.getCustomer();
return customer.getName().equals("unknown") ? 0 : customer.getPaymentHistory().getWeeksDelinquentInLastYear();
}
}
After
먼저, isUnknown() 으로 special case를 추출 후, customer가 unknown인지 판단하는 책임의 위치가 부적절하다고 생각하기 때문에 isUnknown()을 Customer 클래스에서 담당하게 한다.
public class CustomerService {
public String customerName(Site site) {
Customer customer = site.getCustomer();
String customerName;
if (isUnknown(customer)) {
customerName = "occupant";
} else {
customerName = customer.getName();
}
return customerName;
}
public BillingPlan billingPlan(Site site) {
Customer customer = site.getCustomer();
return isUnknown(customer) ? new BasicBillingPlan() : customer.getBillingPlan();
}
public int weeksDelinquent(Site site) {
Customer customer = site.getCustomer();
return isUnknown(customer) ? 0 : customer.getPaymentHistory().getWeeksDelinquentInLastYear();
}
// Customer class로 이동
private boolean isUnknown(Customer customer) {
return customer.getName().equals("unknown");
}
}
public class UnknownCustomer extends Customer {
public UnknownCustomer() {
super("unknown", null, null);
}
@Override
public boolean isUnknown() {
return true;
}
}
특수 경우의 클래스를 Site에서 판단하여 사용하도록 하면 된다.
public class Site {
private Customer customer;
public Site(Customer customer) {
this.customer = customer.getName().equals("unknown") ? new UnknownCustomer() : customer;
}
public Customer getCustomer() {
return customer;
}
}
After(추가)
"UnknownCustomer"가 가지는 상태를 따로 추출해낸 클래스에서 지정해준다면, CustomerService에서 따로 조건문을 통해 판단할 필요도 없어진다.
weeksDelinquent()의 코드는 Null Object Pattern을 적용할 수 있다.
public class CustomerService {
public String customerName(Site site) {
return site.getCustomer().getName();
}
public BillingPlan billingPlan(Site site) {
return site.getCustomer().getBillingPlan();
}
public int weeksDelinquent(Site site) {
return site.getCustomer().getPaymentHistory().getWeeksDelinquentInLastYear();
}
}
public class UnknownCustomer extends Customer {
public UnknownCustomer() {
super("unknown", new BasicBillingPlan(), new NullPaymentHistory());
}
@Override
public boolean isUnknown() {
return true;
}
@Override
public String getName() {
return "occupant";
}
}
public class NullPaymentHistory extends PaymentHistory {
public NullPaymentHistory() {
super(0);
}
}
냄새 16. 임시 필드(Temporary Field)
클래스 추출하기 (Extract Class)
를 사용해 해당 변수들을 옮길 수 있다.함수 옮기기 (Move Function)
을 사용해서 해당 변수를 사용하는 함수를 특정 클래스로 옮길 수 있 다.특이 케이스 추가하기 (Introduce Special Case)
를 적용해 “특정한 경우”에 해당하는 클래스를 만들어 해당 조건을 제거할 수 있다.리팩토링 36. 특이 케이스 추가하기 (Introduce Special Case)
예시
Before
customer.getName().equals("unknown")
과 같은 special case 판단 유무가 반복되며 그에 따른 로직이 달라지는 것을 확인할 수 있다.클래스 추출하기
로 특정한 경우에 해당하는 클래스를 만들어 special case를 제거할 수 있다.After
먼저,
isUnknown()
으로 special case를 추출 후, customer가 unknown인지 판단하는 책임의 위치가 부적절하다고 생각하기 때문에isUnknown()
을 Customer 클래스에서 담당하게 한다.특수 경우의 클래스를 Site에서 판단하여 사용하도록 하면 된다.
After(추가)
weeksDelinquent()
의 코드는 Null Object Pattern을 적용할 수 있다.