✍️ 악취 22 : 서로 다른 인터페이스의 대안 클래스
유사한 기능과 목적을 가진 클래스가 서로 다른 인터페이스를 구현하고 있다면 그 목적은 유사할지언정 상호 대체가 불가능한 구조로, 다르게 말하면 다형성을 활용할 수 없는 문제점이 발생한다. 비슷한 일을 서로 다른 규악으로 지원한다면 관리 문제도 발생한다.
중복된 인터페이스를 제거하고 '함수 선언 변경하기'와 '함수 옮기기'를 사용해서 하나의 인터페이스만을 구현하도록 구조를 변경할 수 있다. 하지만 인터페이스를 수정할 수 없는 상황도 존재한다. 외부 모듈 혹은 라이브러리를 참조하는 케이스가 그 대표적인 예시이다. 본 포스팅에선 인터페이스의 수정이 불가능한 상황에서 리팩토링을 적용하는 예제를 소개하려 한다.
🍊 예제 : 적용 전
가정해 봅시다. '이메일을 전송하는 기능'과 '알람을 전송하는 기능'이 서로 다른 인터페이스로 정의되었음을.
public interface EmailService {
void sendEmail(EmailMessage emailMessage);
}
public interface AlertService {
void add(AlertMessage alertMessage);
}
public class EmailMessage {
private String title;
private String to;
private String from;
}
public class AlertMessage {
private String message;
private String to;
}
비슷하지만 서로 다른 인터페이스로 그 기능을 제공하고 있다면 다형성을 활용할 수 없고 관리가 어렵다는 문제가 있다.
public class OrderManager {
private AlertService alertService;
public void alertOrder(Order order) {
AlertMessage alertMessage = AlertMessage.builder()
.message(order.getOrder() + "is ordered")
.to(order.getEmail())
.build();
alertService.add(alertMessage);
}
}
public class ShippingManager {
private EmailService emailService;
public void notifyShipping(Shipping shipping) {
EmailMessage emailMessage = EmailMessage.builder()
.title(shipping.getOrder() + "is shipped")
.to(shipping.getEmail())
.from("no-reply@knagworld.co.kr")
.build();
emailService.sendEmail(emailMessage);
}
}
🌱 예제 : 적용 후
인터페이스를 변경하는 것이 베스트지만, 그렇지 못한 케이스에선 '어댑터 패턴'을 적용하는 것도 하나의 방법이 될 수 있다.
Order와 Shipping을 감싸는 인터페이스를 정의한다.
public interface Notification {
}
각각의 구현체는 AlertMessage와 EmailMessage를 제공한다.
@AllArgsConstructor
public class AlertNotification implements Notification {
private Order order;
public AlertMessage toAlertMessage() {
return AlertMessage.builder()
.message(order.getOrder() + " is ordered")
.to(order.getEmail())
.build();
}
}
@AllArgsConstructor
public class EmailNotification implements Notification {
private Shipping shipping;
public EmailMessage toEmailMessage() {
return EmailMessage.builder()
.title(shipping.getOrder() + " is shipped")
.from("no-reply@kangworld.co.kr")
.to(shipping.getEmail())
.build();
}
}
클라이언트는 추상화된 Notification과 NotificationService 만으로 코드를 전개하고, 구체적인 AlertMessage와 AlertService, EmailMessage와 EmailService는 구현체에서 처리하도록 변경한다.
public interface NotificationService {
void sendNotification(Notification notification);
}
public class OrderManager {
private NotificationService notificationService;
public void alertOrder(Order order) {
Notification notification = new AlertNotification(order);
notificationService.sendNotification(notification);
}
}
public class AlertNotificationService implements NotificationService {
private AlertService alertService;
@Override
public void sendNotification(Notification notification) {
AlertNotification alertNotification = (AlertNotification) notification;
alertService.add(alertNotification.toAlertMessage());
}
}
'리팩토링' 카테고리의 다른 글
[리팩토링] 악취 24 : 상속 포기 (0) | 2023.02.15 |
---|---|
[리팩토링] 악취 23 : 데이터 클래스 (0) | 2023.02.14 |
[리팩토링] 악취 21 : 거대한 클래스 (0) | 2023.02.12 |
[리팩토링] 악취 20 : 내부자 거래 (0) | 2023.02.11 |
[리팩토링] 악취 19 : 상속 (2) (0) | 2023.02.10 |