개발자의 오르막

[비사이드 #4] 생성자에 코드를 넣지 말아주세요. 본문

Toy Project/비사이드

[비사이드 #4] 생성자에 코드를 넣지 말아주세요.

계단 2022. 6. 11. 19:07

 

1장_3절. 생성자에 코드를 넣지 마세요

  • 주 생성자는 객체 초기화 프로세스를 시작하는 유일한 장소이기 때문에 제공되는 인자들은 완전해야 합니다.
  • 객체 초기화에는 코드가 없어야 한다.
  • 주 생성자에는 코드가 없어야 하고, 오직 할당문만 포함해야 한다.
  • 진정한 객체지향에서 인스턴스화란 더 작은 객체들을 조합해서 더 큰 객체를 만드는 것을 의미한다.

 

그렇다면 위드펫에서 위에 해당하는 부분이 있을까?

 

공공데이터 URL : https://www.data.go.kr/data/15098931/openapi.do

 

상황

  • 현재 프로젝트에서는 공공데이터를 파싱하여 AdoptAnimalData 테이블로 저장 후 AdoptAnimal 테이블로 가공하여 저장하는 형태로 구성되어 있다.
  • 공공데이터에서는 모든 필드값을 string 으로 내려주는데, 그렇기 때문에 Enum , LocalDate 타입 형태로 가공이 필요한 데이터들이 존재했다.

 

 

  • AdoptAnimal Entity
public static AdoptAnimal insertPublicData(AnimalProtectDto dto, Long id, String upKindCd) {

        AdoptAnimal adoptAnimal = AdoptAnimal.builder()
                .id(id)
                .noticeNo(dto.getNoticeNo())
                .noticeStartDate(dto.getNoticeStartDate()).noticeEndDate(dto.getNoticeEndDate()
                .build();

        adoptAnimal.setProcessStatus(dto);

        return adoptAnimal;
    }

private void setProcessStatus(AnimalProtectDto dto) {
        if (dto.getProcessState().equals("보호중")) {
            if (LocalDate.now().isAfter(dto.getNoticeEndDate())) {
                this.processStatus = ProcessStatus.PROTECT;
            } else {
                this.processStatus = ProcessStatus.NOTICE;
            }
        }

        if (dto.getProcessState().contains("종료")) {
            this.processStatus = ProcessStatus.TERMINAL;
        }

 }
  • 현재 주 생성자에 코드가 들어가 있는 모습이다.
  • AnimalProtectDto 는 위의 공공데이터를 받아온 Dto 이고, 이를 Entity 주 생성자에 사용하고 있는 모습이다.
  • 현재 noticeSdt , noticeEdt , processState 3개의 필드는 String 값으로 공공데이터에서 내려주고 있으며, 위 3개 필드는 가공을 통해 엔티티에서 초기화된 객체를 구성할 수 있어야 한다.

 

 

 

저 코드를 작성했을 때의 나의 생각…

  • 캡슐화에 대한 생각..
    • 초기 데이터를 생성할 때의 동작은 엔티티 안에서 이루어져야 한다고 생각했다. 엔티티의 생성자는 공공데이터인 AnimalProtectDto 를 인자값으로 받고, ProcessStatus 란 값을 넣어주는 메서드는 AdoptAnimal 엔티티 안에 있길 원했다.
    • 밖에서 AdoptAnimal 을 바라봤을 때는 그저 객체를 생성한다라는 사실만 알 수 있게끔 하고 싶었다.
    • 하지만 ProcessStatus 는 필수값이기 때문에 setProcessStatus(AnimalProtectDto dto 을 엔티티 밖에서 호출하고 싶지 않았고, 결국 생성자 안에서 호출하는 방식으로 필수값인 ProcessStatus 를 넣어주는 방식을 선택하게 됐었다.

 

리펙토링에 대한 근거

  • 주 생성자는 객체 초기화 프로세스를 시작하는 유일한 장소이기 때문에 제공되는 인자들은 완전해야 합니다.
  • 주 생성자에는 코드가 없어야 하고, 오직 할당문만 포함해야 한다.
  • 객체는 생성 이후의 역할에 대해서만 관심사를 두어야 한다.

 

 

리펙토링 방향

1. 위의 근거들을 봤을 때, 우선 첫 번째로 관심사의 분리가 필요한 것을 알 수 있었다.

  • AnimalProtectDto 는 공공데이터 API 에서 받아온 데이터이다. 이에 대한 정보를 Entity 에 맞게 가공하기 위한 작업을 AdoptAnimal 엔티티가 수행해야 할 역할이 아니라는 것이다.
  • AdoptAnimal 객체가 생성되기 이전의 작업들은 다른 객체가 할 수 있도록 관심사를 분리해주자

  • AnimalProtectDto 의 역할 : 공공데이터 API Response
  • VO 의 역할 : AdoptAnimal 필드를 가지고 있으며, 여러 생성자를 통해 AdoptAnimal 에 맞게 데이터를 가공하여 주입하는 것
  • AdoptAnimal 객체로서 서비스에 기능을 제공

 

 

2. 두 번째로, Enum 객체가 String to Enum 역할을 할 수 있어야 한다.

  • String 인자값을 받았을 때의 맞는 Enum 을 Return 해주는 코드는 해당 Enum 에 있어야 한다.

 

 


 

리펙토링 결과

AdoptAnimalVo vo = new AdoptAnimalVo(dto, upKindCd);

Optional<AdoptAnimal> findAdoptAnimal = adoptAnimalRepository.findAdoptAnimalByNoticeNoAndDeleteYn(vo.getNoticeNo(), YnType.N);
if (findAdoptAnimal.isPresent()) {
	if (findAdoptAnimal.get().isNotNeedUpdate(vo)) {
		continue;
	}
}

Long adoptAnimalId = findAdoptAnimal.map(AdoptAnimal::getId).orElse(null);
AdoptAnimal adoptAnimal = AdoptAnimal.insertPublicData(vo, adoptAnimalId);
adoptAnimalRepository.save(adoptAnimal);
  • AnimalProtectDto 는 AdoptAnimalVo 로 변환하는 코드가 추가되었다.
  • vo 는 Entity 와 같은 필드로 고정되며, vo 의 생성자는 인자값에 따라 필드값에 데이터를 주입한다.

 

this.processStatus = ProcessStatus.findByCodeAndNoticeEndDate(dto.getProcessState(), this.noticeEndDate);
this.terminalState = TerminalStatus.findByTerminalState(dto.getProcessState());
this.animalKindType = AnimalKindType.findByUpKindCd(upKindCd);
  • AdoptAnimalVo 에서는 인자값을 기준으로 Enum 을 반환하는 메서드가 Enum 안으로 들어가게 되었다.

 

 

 

이로인해 AdoptAnimal Entity 의 생성자의 코드는 없앨 수 있었다.

Comments