개발자의 오르막
[비사이드 #4] 생성자에 코드를 넣지 말아주세요. 본문
1장_3절. 생성자에 코드를 넣지 마세요
- 주 생성자는 객체 초기화 프로세스를 시작하는 유일한 장소이기 때문에 제공되는 인자들은 완전해야 합니다.
- 객체 초기화에는 코드가 없어야 한다.
- 주 생성자에는 코드가 없어야 하고, 오직 할당문만 포함해야 한다.
- 진정한 객체지향에서 인스턴스화란 더 작은 객체들을 조합해서 더 큰 객체를 만드는 것을 의미한다.
그렇다면 위드펫에서 위에 해당하는 부분이 있을까?
상황
- 현재 프로젝트에서는 공공데이터를 파싱하여 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 의 생성자의 코드는 없앨 수 있었다.
'Toy Project > 비사이드' 카테고리의 다른 글
[비사이드 #6] 네이버클라우드 적용기 - 공인 IP 생성 및 포트 포워딩, SSH 연결 (0) | 2022.08.28 |
---|---|
[비사이드 #5] 네이버클라우드 적용기 - 웹서버 설치과정, VM 서버 생성방법 (0) | 2022.07.31 |
[비사이드 #3] Github Issue 를 활용한 팀프로젝트 환경 셋팅 (0) | 2022.05.21 |
[비사이드 #2] 아이디어 선정 및 아이디어 구체화 (0) | 2022.05.15 |
[비사이드 #1] 비사이드 참여 동기와 사전 기술 SPEC 정의 (0) | 2022.04.22 |
Comments