본문 바로가기
프로젝트 기록/Spring

[Spring / Project] 응답 객체에서 상속 구조를 사용해보자

by clean01 2024. 5. 13.

이번 글은 어떤 지식을 전하는 글이라기 보단, 프로젝트를 하면서 제가 놓치고 있었던 부분에 대한 회고록에 가깝습니다,,ㅎㅎ

멘토님의 피드백을 받고 느끼게 된 것은 '내가 정말 자바의 상속을 이용하지 못하고 있구나'입니다.
제가 자바를 좋아하는 이유는 객체지향을 잘 지원해주는 언어이기 때문이라고 생각했는데, 정작 프로젝트를 하면서 상속을 거의 이용하지 않고 있었다는 것이 부끄러워졌습니다.

멘토님께서 피드백 주신 것이, 일단 응답 객체인 FailureResult, SuccessResult가 상속 구조로 변경할 수 있을 것 같으니 이 둘을 상속 구조로 바꿔보라는 것이었습니다.

오늘 포스트는 이 두 객체를 상속 구조로 바꾸는 일종의 리팩토링(?) 과정에 대해 기록해보려고 합니다.

원래는 어떻게 되어 있었는데?

원래 코드는 아래와 같습니다.

FailureResult.java

package com.mewsinsa.global.response;

public class FailureResult {
  private DetailedStatus status;
  private String code;
  private String message;

  //==Constructor==//
  // builder를 통해서만 생성되도록 private으로 정의
  private FailureResult(Builder builder) {
    this.status = builder.status;
    this.code = builder.code;
    this.message = builder.message;
  }

  //==Getter==//
  public DetailedStatus getStatus() {
    return status;
  }

  public String getMessage() {
    return message;
  }

  public String getCode() {
    return code;
  }

  //==Builder==//
  public static class Builder {
    DetailedStatus status;
    String code;
    String message;

    public Builder() {

    }

    public Builder status(DetailedStatus status) {
      this.status = status;
      return this;
    }
    public Builder message(String message) {
      this.message = message;
      return this;
    }

    public Builder code(String code) {
      this.code = code;
      return this;
    }

    public FailureResult build() {
      return new FailureResult(this);
    }
  }
}

SuccessResult.java

package com.mewsinsa.global.response;

public class FailureResult {
  private DetailedStatus status;
  private String code;
  private String message;

  //==Constructor==//
  // builder를 통해서만 생성되도록 private으로 정의
  private FailureResult(Builder builder) {
    this.status = builder.status;
    this.code = builder.code;
    this.message = builder.message;
  }

  //==Getter==//
  public DetailedStatus getStatus() {
    return status;
  }

  public String getMessage() {
    return message;
  }

  public String getCode() {
    return code;
  }

  //==Builder==//
  public static class Builder {
    DetailedStatus status;
    String code;
    String message;

    public Builder() {

    }

    public Builder status(DetailedStatus status) {
      this.status = status;
      return this;
    }
    public Builder message(String message) {
      this.message = message;
      return this;
    }

    public Builder code(String code) {
      this.code = code;
      return this;
    }

    public FailureResult build() {
      return new FailureResult(this);
    }
  }
}

코드가 엄청 긴건 그냥 롬복 안써서 그래요.. 아마도.. 일부러 안쓴거예요

변수 선언 부분만 보시면

 


이렇게 노란색 박스로 표시된 부분이 겹친다는 것을 확인하실 수 있습니다.

이 겹치는 부분을 부모 클래스인 FailureResult에 넣어두고 자식 클래스인 SuccessResult에는 data 필드만 남기려고 합니다.
또한 위에 사진에서는 SuccessResult에는 code라는 필드가 없지만 상속 구조로 바꾸면서 SuccessResult에도 code가 추가되도록 수정할 예정입니다.
즉 제가 수정하려고 하는 내용을 정리하면 아래와 같습니다.

고치는 과정

  1. FailureResult를 부모 클래스로 할 것이므로 이름을 ResponseResult로 변경해줍니다. (Success가 Failure를 상속 받는 것은 의미상 이상하니까..)
  2. ResponseResult에는 status, code message 필드를 남기고 이 필드의 접근제어자를 모두 protected로 변경해줍니다. (자식 클래스에서 접근할 수 있도록)
  3. message 필드는 상황에 따라 응답 값에 포함될 수도, 아닐 수도 있기 때문에 @JsonInclude(Include.NON_NULL) 어노테이션을 붙여줍니다.
  4. ResponseResult에 매개변수가 없는 생성자를 만들어주고 접근제어자를 protected로 설정해줍니다. (자식 클래스에서 super()를 호출할 수 있도록)
  5. SuccessResultResponseResult를 상속 받도록 합니다.
  6. SuccessResult에는 data 필드만 남기고, 상황에 따라 응답 데이터가 없을 수도 있으므로 @JsonInclude(Include.NON_NULL)를 붙여줍니다.

결과물

위와 같은 과정을 거쳐서 수정한 두 클래스는 아래와 같습니다.

ResponseResult.java

package com.mewsinsa.global.response;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;

public class ResponseResult {
  protected DetailedStatus status;
  protected String code;

  @JsonInclude(Include.NON_NULL)
  protected String message;

  //==Constructor==//
  // builder를 통해서만 생성되도록 private으로 정의
  protected ResponseResult(Builder builder) {
    this.status = builder.status;
    this.code = builder.code;
    this.message = builder.message;
  }

  protected ResponseResult() {
  }

  //==Getter==//
  public DetailedStatus getStatus() {
    return status;
  }

  public String getMessage() {
    return message;
  }

  public String getCode() {
    return code;
  }

  //==Builder==//
  public static class Builder {
    DetailedStatus status;
    String code;
    String message;

    public Builder() {

    }

    public Builder status(DetailedStatus status) {
      this.status = status;
      return this;
    }
    public Builder message(String message) {
      this.message = message;
      return this;
    }

    public Builder code(String code) {
      this.code = code;
      return this;
    }

    public ResponseResult build() {
      return new ResponseResult(this);
    }
  }
}

SucceessResult.java

package com.mewsinsa.global.response;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import org.springframework.web.bind.MethodArgumentNotValidException;

public class SuccessResult extends ResponseResult {
  @JsonInclude(Include.NON_NULL)
  private Object data;

  //==Constructor==//
  // builder를 통해서만 생성되도록 private으로 정의
  protected SuccessResult(Builder builder) {
    this.status = builder.status;
    this.code = builder.code;
    this.message = builder.message;
    this.data = builder.data;
  }

  //==Getter==//
  public DetailedStatus getHttpStatus() {
    return status;
  }

  public String getMessage() {
    return message;
  }

  public Object getData() {
    return data;
  }

  //==Builder==//
  public static class Builder {
    DetailedStatus status;
    String message;
    String code;
    Object data;

    // httpStatus에 대한 정보는 반드시 필요
    public Builder(DetailedStatus status) {
      this.status = status;
    }


    public Builder message(String message) {
      this.message = message;
      return this;
    }

    public Builder data(Object data) {
      this.data = data;
      return this;
    }

    public Builder code(String code) {
      this.code = code;
      return this;
    }

    public SuccessResult build() {
      return new SuccessResult(this);
    }
  }


}

좋은 구조로 잘 고친 것인지는 잘 모르겠습니다,,
아무래도 내부에 static class로 구현되어있는 Builder는 상속을 쓰지 않고 두 클래스에 모두 하드코딩되어있는 상황이긴한데
아무튼 ResponseResultSuccessResult는 상속 관계로 바꾸어주었으니, 조금은 더 자바의 객체 지향을 잘 이용한 코드로 발전하지 않았을까..?하는 생각입니다.

솔직히 말하면, 이미 저 응답 객체들을 Controller 여기저기서 많이 쓴 상태라, 클래스 한번 고치면 몇십 줄의 코드를 더 고쳐야하는 상황에 놓일 수 있어서 조심스러워서 많이 고치지는 못했습니다.
하지만 더 개선할 부분이 있는지 고민해보고, 시간 여유가 될때 바뀐 클래스 구조에 맞게 컨트롤러 코드들도 수정해야겠습니다.