서로 다른 인터페이스의 대안 클래스들이라는 뜻으로
비슷한 일을 여러 곳에서 서로 다른 규약을 사용해 지원하고 있는 코드의 냄새를 말한다.
대안 클래스로 사용하려면 동일한 인터페이스를 구현하고 있어야 한다.
보통 함수 선언 변경하기와 함수 옮기기를 사용하여 서로 동일한 인터페이스를 구현하게끔 코드를 수정할 수 있다.
두 클래스에서 일부 코드가 중복되는 경우에는 슈퍼클래스로 추출하여 중복된 코드를 슈퍼클래스로 옮기고 두 클래스를 새로운 슈퍼클래스의 서브클래스로 만들 수 있다.
산발적으로 비슷한 기능을 조금씩 다르게 만드는 코드가 보인다면 다음 리팩토링을 진행해 보자.
public class OrderAlerts {
private AlertService alertService;
public void alertShipped(Order order) {
AlertMessage alertMessage = new AlertMessage();
alertMessage.setMessage(order.toString() + " is shipped");
alertMessage.setFor(order.getEmail());
alertService.add(alertMessage);
}
}
public class OrderProcessor {
private EmailService emailService;
public void notifyShipping(Shipping shipping) {
EmailMessage emailMessage = new EmailMessage();
emailMessage.setTitle(shipping.getOrder() + " is shipped");
emailMessage.setTo(shipping.getEmail());
emailMessage.setFrom("no-reply@whiteship.com");
emailService.sendEmail(emailMessage);
}
}
OrderAlerts 클래스와 OrderProcessor 클래스의 두 메서드는 구현은 다르지만 동일하게 알람을 이메일로 전송하는 기능을 하고 있다.
public interface EmailService {
void sendEmail(EmailMessage emailMessage);
}
public interface AlertService {
void add(AlertMessage alertMessage);
}
하지만 두 인터페이스는 서로 다른 메서드를 정의하고 있다. 만약 해당 인터페이스를 고칠 수 없는 경우 어떻게 해야 할까?
나는 어댑터 패턴이 떠오르긴 한다.
추상화 계층을 하나 더 만들어 기존 API를 감싸보자.
public interface NotificationService {
public void sendNotification(Notification notification);
}
public class OrderProcessor {
private EmailService emailService;
private NotificationService notificationService;
public OrderProcessor(NotificationService emailService) {
this.notificationService = emailService;
}
public void notifyShipping(Shipping shipping) {
Notification notification = Notification.newNotification(shipping.getOrder() + "is shipped")
.receiver(shipping.getEmail());
notificationService.sendNotification(notification);
}
}
기존에 있던 구현 내용은 NotificationService의 구현체로 옮겨준다.
public class EmailNotificationService implements NotificationService{
private EmailService emailService;
@Override
public void sendNotification(Notification notification) {
EmailMessage emailMessage = new EmailMessage();
emailMessage.setTitle(notification.getTitle());
emailMessage.setTo(notification.getReceiver());
emailMessage.setFrom(notification.getSender());
emailService.sendEmail(emailMessage);
}
}
비슷하게 OrderAlerts도 고쳐보자.
public class AlertsNotificationService implements NotificationService{
private AlertService alertService;
public AlertsNotificationService(AlertService alertService) {
this.alertService = alertService;
}
@Override
public void sendNotification(Notification notification) {
AlertMessage alertMessage = new AlertMessage();
alertMessage.setMessage(notification.getTitle());
alertMessage.setFor(notification.getSender());
alertService.add(alertMessage);
}
}
public class OrderAlerts {
private NotificationService notificationService;
public OrderAlerts(NotificationService notificationService) {
this.notificationService = notificationService;
}
public void alertShipped(Order order) {
Notification notification = Notification.newNotification(order.toString() + "is shipped")
.receiver(order.getEmail());
notificationService.sendNotification(notification);
}
}
기존 서로 다른 인터페이스를 사용하던 기능에 대해 NotificationService라는 인터페이스를 중재자로 써 사용했다.
이로 인해 클라이언트는 실제 NotificationService의 구현체를 원하는 대로 주입할 수 있고 이를 동일 인터페이스로 사용할 수 있다.
만약 인터페이스를 수정할 수 있는 권한이 있다면 인터페이스를 동일한 기능을 제공하도록 고친 이후 사용하는 클래스들을 리팩터링 해주는 것이 맞다.
다만 해당 인터페이스를 수정할 수 없는 경우. 외부 라이브러리를 사용하는데 테스트코드를 작성하며 이에 대해 비슷한 기능을 한다고 생각되면 중간에 래핑 하여 사용할 수 있다.
기존 클라이언트는 EmailService, AlertService를 사용하는 부분에서 해당 서비스를 주입하여 래핑 된 NotificationService를 구현한 구현체로 변경할 수 있으며 그 방법 또한 매우 간단하다.
서로 다른 인터페이스가 동일한 기능을 제공하는 것처럼 보인다면 해당 인터페이스를 포괄할 수 있는 인터페이스를 생성하고
과거 인터페이스를 사용하는 클라이언트 코드를 래핑 인터페이스를 사용하도록 구현하자.
이후 기존 코드는 래핑 인터페이스를 구현한 구현체에서 구현하도록 하자.
'독서에서 한걸음' 카테고리의 다른 글
Comments (0) | 2022.12.28 |
---|---|
Refused Bequest (0) | 2022.12.28 |
Large Class (0) | 2022.12.26 |
Insider Trading (0) | 2022.12.26 |
Middle Man (0) | 2022.12.24 |
댓글