DEV ℧ Developer Diary

[Refactoring] 냄새 21. 서로 다른 인터페이스의 대안 클래스들

해당 포스트는 inflearn의 백기선님의 강의인 리팩토링 을 듣고 정리한 글입니다.

냄새 21. 서로 다른 인터페이스의 대안 클래스들

  • 비슷한 일을 여러 곳에서 서로 다른 규약을 사용해 지원하고 있는 코드 냄새.
  • 대안 클래스로 사용하려면 동일한 인터페이스를 구현하고 있어야 한다.
  • “함수 선언 변경하기 (Change Function Declaration)”와 “함수 옮기기 (Move Function)”을 사용해서 서로 동일한 인터페이스를 구현하게끔 코드를 수정할 수 있다.
  • 두 클래스에서 일부 코드가 중복되는 경우에는 “슈퍼클래스 추출하기 (Extract Superclass)”를 사용해 중복된 코드를 슈퍼클래스로 옮기고 두 클래스를 새로운 슈퍼클래스의 서브클래스로 만들 수 있다.

예제 코드

충분한 협의가 되지않아, 비슷한 기능이 인터페이스로 구현되었을 경우 합치는 방법이다.

예제코드를 살펴보면, 둘의 기능이 각각 이메일 발송과, 알림 발송이지만 비슷한 기능으로 돌아가는 것을 볼 수있다.

두 기능을 슈퍼클래스로 묶어 정의해보고자 한다.

  • EmailService, OrderProcessor
public interface EmailService {
  void sendEmail(EmailMessage emailMessage);
}

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);
    }
}
  • AlertService, OrderAlerts
public interface AlertService {
  void add(AlertMessage alertMessage);
}

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 Notification {

    private String title;
    private String receiver;
    private String sender;
    ...
}

이후 해당 정보를 발송할 슈퍼클래스를 만들어준다.

public interface NotificationService {
    void sendNotification(Notification notification);
}

먼저 이메일의 기능을 변환해보자.

sendNotification 에서 부여하던 제목 수신자 발신자의 정보를 Notification 클래스에 담아준다.

public class OrderProcessor {

    private EmailService emailService;

    public void notifyShipping(Shipping shipping) {
        Notification notification = Notification.newNotification(shipping.getOrder() + " is shipped")
                        .receiver(shipping.getEmail())
                        .sender("no-reply@whiteship.com");
        sendNotification(shipping);
    }

    ...
}

이후 방금 생성한 NotificationService를 상속하는 구현체를 만들어 sendNotification의 메소드를 옮겨주도록 하자.

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);
    }
}

그러면 기존의 이메일 발송 로직은 필요없는 부분을 지워 간단하게 만들어 줄 수 있다.

public class OrderProcessor {
    private NotificationService notificationService;
    public void notifyShipping(Shipping shipping) {
        Notification notification = Notification.newNotification(shipping.getOrder() + " is shipped")
                        .receiver(shipping.getEmail())
                        .sender("no-reply@whiteship.com");
        notificationService.sendNotification(notification);
    }
}

알림 관련 부분도 동일하게 적용 해 줄 수있다.

public class OrderAlerts {

    private NotificationService notificationService;

    public void alertShipped(Order order) {
        Notification notification = Notification.newNotification(order.toString() + " is shipped")
                        .receiver(order.getEmail());
        notificationService.sendNotification(notification);
    }

}
public class AlertNotificationService implements NotificationService{

    private AlertService alertService;

    @Override
    public void sendNotification(Notification notification) {
        AlertMessage alertMessage = new AlertMessage();
        alertMessage.setMessage(notification.getTitle());
        alertMessage.setFor(notification.getReceiver());
        alertService.add(alertMessage);
    }
}