본문 바로가기
Lecture

자바 코드 리팩토링: 객체의 협력 관계를 디자인해보자.

by Renechoi 2023. 7. 9.

 

문제점 

 

다음과 같은 주문 객체가 있다고 해보자. 

 

public class OrderLegacy {

   private long id;
   // KAKAO, SMS, EMAIL 등 메세지 플랫폼등이 있음
   private String messageTypes;

   public OrderLegacy(String messageTypes) {
      this.messageTypes = messageTypes;
   }

   public String[] getMessageTypes() {
      return messageTypes.split(",");
   }
}

 

이 코드의 문제점은 무엇일까? 

 

messageType에 다양한 플랫폼들이 적용되어야 하며 여러 케이스에 대한 검증이 필요하다. 다음과 같은 경우이다. 

 

 

@Test
public void KAKAO를_KAOKO_라고_잘못_입력했을_경우() {
    final OrderLegacy orderLegacy = new OrderLegacy("KAOKO,EMAIL,SMS");
    final String[] split = orderLegacy.getMessageTypes();

    then(split).doesNotContain("KAKAO");
    then(split).contains("EMAIL", "SMS");
}

@Test
public void 메시지에_KAKAO_EMAIL_SMS_처럼_공백이_들어_간다면_실패한다() {
    final OrderLegacy orderLegacy = new OrderLegacy("KAKAO, EMAIL, SMS");
    final String[] split = orderLegacy.getMessageTypes();

    then(split).contains("KAKAO");
    then(split).doesNotContain("EMAIL", "SMS");
}

@Test
public void 메시지가_없을_때_빈문자열을_보낼_경우() {
    final OrderLegacy orderLegacy = new OrderLegacy("");
    final String[] split = orderLegacy.getMessageTypes();

    then(split).contains("");
}

@Test
public void 메시지가_없을_때_null_보내는_경우() {
    final OrderLegacy orderLegacy = new OrderLegacy(null);
    thenThrownBy(() -> orderLegacy.getMessageTypes())
        .isInstanceOf(NullPointerException.class);

}

@Test
public void 메시지가_중복으로_올경우() {
    final OrderLegacy orderLegacy = new OrderLegacy("KAKAO, KAKAO, KAKAO");
    final String[] split = orderLegacy.getMessageTypes();

    then(split).contains("KAKAO");
    then(split).hasSize(3);
}

 

 

이렇게 다양한 경우의 수에 따라 객체가 계속해서 검증을 해주어야 하므로 주문 객체의 책임의 측면에서 적절하지 못하다. 

 

이는 SOLID 에서 단일 책임 원칙 위배한다고 볼 수 있는데, 

 

1) OrderLegacy는 주문을 나타내는 것 외에도 메시지 유형을 처리하는 책임을 가지고 있기 때문이다. 즉, 주문 객체의 책임이 메시지 유형 처리와 같은 다른 영역과 혼합되어 있다. 객체는 하나의 기능 또는 책임만을 가져야 한다는 객체 지향 설계 원칙에 위배된다.

2) OrderLegacy는 하나의 문자열로 여러 메시지 유형을 표현하고 있다. 이러한 접근 방식은 다양한 메시지 유형이 추가되거나 변경될 때 확장성이 부족하며, 유효성 검사와 같은 추가 로직을 수행하기 어렵다는 측면에서 좋지 못한 설계이다. 


OrderLegacy 클래스는 이러한 문제들을 가져 책임의 분리와 확장성, 유효성 검사 등의 관점에서 개선의 여지가 있다. 

 

 

 

제안: 객체 협력 관계 디자인

 

우선 Order 즉 주문 정체성에 맞는 일만 하도록 객체를 다음과 같이 재디자인해보자.

 

메시지 타입이 다음과 같이 존재한다고 할 때 

 

public enum MessageType {
    EMAIL, SMS, KAKAO;
}

 

이때 객체가 MessageType을 검증하는 것이 아니라 해당 MessageType을 그냥 받도록 한다. OrderMessage 라는 새로운 객체를 통해서 필요한 메시지를 전달 받는다. 

 

@Getter
public class Order {

   private OrderMessage message;

   public Order(OrderMessage orderMessage) {
      this.message = orderMessage;
   }
}

 

이제 OrderMessage라는 새로운 객체가 MessageType을 검증하여 필요한 메시지를 전달해주면 된다. 즉, MessageType에 대한 정제 기능을 기존에는 Order 클래스가 담당해씨지만 이제 그 역할과 책임을 OrderMessage에게 넘겼다. 

 

public class OrderMessage {

   private String type;

   private OrderMessage(String type) {
      this.type = ObjectUtils.isEmpty(type) ? null : type;
   }

   public static OrderMessage of(Set<MessageType> types) {
      return new OrderMessage(joining(types));
   }

   public List<MessageType> getTypes() {
      if (ObjectUtils.isEmpty(type)) {
         return new ArrayList<>();
      }

      return new ArrayList<>(doSplit());
   }

   private static String joining(Set<MessageType> types) {
      return types.stream()
         .map(Enum::name)
         .collect(Collectors.joining(","));
   }

   private Set<MessageType> doSplit() {
      final String[] split = this.type.split(",");
      return Arrays.stream(split)
         .map(MessageType::valueOf)
         .collect(Collectors.toSet());
   }
}

 

OrderMessage는 Set 자료구조를 가지면서 들어오는 MessageType에 대한 파싱 역할을 담당한다.

 

OrderMessage 클래스는 메시지 유형을 표현하는 열거형 MessageType의 Set을 입력으로 받아 이를 문자열로 변환하는 'joining' 메소드와, 문자열로 저장된 메시지 유형을 다시 MessageType의 Set으로 변환하는 'doSplit' 메소드를 갖는다.

 

 'joining' 메소드에서는 입력으로 받은 Set<MessageType>을 스트림으로 변환한 후, 각 MessageType을 그 이름으로 매핑하고, 이를 콤마로 연결하여 문자열로 반환한다. 


 'doSplit' 메소드에서는 문자열로 저장된 메시지 유형을 콤마를 기준으로 분리한 후, 각 문자열을 MessageType으로 변환하고, 이를 Set으로 수집하여 반환한다.


이렇게 함으로써 OrderMessage 클래스는 메시지 유형을 표현하는 문자열과 MessageType의 Set 사이에서 변환을 담당한다. 결과적으로 메시지 유형의 검증과 변환 역할을 OrderLegacy에서 분리하며, 이로써 Order는 단일 책임 원칙을 준수하게 되었다.

 

 

결론

 

이렇게 필요한 역할을 OrderMessage에게 위임하며, 두 객체 Order와 OrderMessage는 협력적으로 메시지를 주고 받는 설계를 할 수 있다. 

 

 


 

참고자료

- 패스트 캠퍼스 (한 번에 끝내는 Spring 완.전.판 초격차 패키지 Online - Part9)

 

 

 

 

 

반응형