인터페이스를 파라미터 타입으로 선언했을 때의 장점

Mando·2023년 11월 26일
3

골라주마

목록 보기
1/2
post-thumbnail

오늘, 카테캠 3단계 프로젝트 팀원이 "supplier를 사용하신 이유가 뭐에요?"라는 질문이 들어왔다.

대면으로 질문이 들어왔었는데, 그 자리에서 깔끔하게 설명하지 못 한게 아쉬워서 이 글을 통해 설명을 다시하고자 정리한다.

상황 설명

아래와 같이 나는 NicknameSupplier라는 인터페이스를 만들었다.

package com.kakao.golajuma.auth.web.supplier;

public interface NicknameSupplier {
	String getNickname();
}

그리고 이를 ValidNicknameRequest에서 implements하여 사용한다.

@Getter
@AllArgsConstructor
@NoArgsConstructor
@Builder(toBuilder = true)
public class ValidNicknameRequest implements NicknameSupplier {
	@NotBlank(message = ValidExceptionMessage.EMPTY_MESSAGE)
	String nickname;
}

그리고 이는 ValidNicknameRequest를 response body에서 받아서 이에 해당하는 service로 호출하는 코드이다.

	@PostMapping("/nickname-check")
	public ApiResponse<SuccessBody<Void>> validNickname(
			@RequestBody @Valid ValidNicknameRequest request) {
		validNicknameService.execute(request);
		return ApiResponseGenerator.success(HttpStatus.CREATED, MessageCode.CREATE);
	}

여기서, "특이점" 이라고 한다면,
execute()의 파라미터로 ValidNicknameRequest를 받는 게 아니라 NicknameSupplier 를 받는 것이라고 할 수 있다.

@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class ValidNicknameService {
	private final UserRepository userRepository;

	public void execute(NicknameSupplier supplier) {
		boolean exists = userRepository.existsByNickname(supplier.getNickname());
		if (exists) {
			throw new DuplicatedNicknameException();
		}
	}
}

이 글을 보는 모든 분들이 똑같은 의문이 들 것이다.
implements NicknameSupplier를 하지 않고, 파라미터로 ValidNicknameRequest 타입을 전달받아도 되었을 텐데,

왜 나는 implements NicknameSupplier를 사용했으며, NicknameSupplier를 파라미터 타입으로 전달받았을까?

인터페이스를 파라미터 타입으로 선언했을 때의 장점

인터페이스를 파라미터로 타입으로 선언을 하면, 재사용성이 높아진다!

만약, ValidNicknameRequest를 파라미터의 타입으로 선언을 해두었다면,
해당 메서드에는 ValidNicknameRequest 클래스 아니면 ValidNicknameRequest를 상속한 클래스밖에 오지 못 한다.

@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class ValidNicknameService {
	private final UserRepository userRepository;

	public void execute(ValidNicknameRequest request) {
		boolean exists = userRepository.existsByNickname(request.getNickname());
		if (exists) {
			throw new DuplicatedNicknameException();
		}
	}
}

그래서,
만약, ValidNicknameRequest에서도 valid작업을 우선해야하기 때문에 위와 동일한 로직을 수행할 텐데, 그럼 이때는 ValidNicknameRequest타입을 파라미터로 받는 메서드를 하나 더 추가해야 한다.
(물론, 이때 그냥 String을 넘기면 되는 거 아니야? 라고 생각할 수가 있다. 지금은 String만 넘겨도 되지만.. 언젠가는 또 객체 형태도 있지 않을까?)

이런 방식의 장점은 무엇이 있을까?

이처럼 추상화를 통해 내부에 어떤 멤버변수를 갖고 있던 관련 없이 필요한 정보만 건네줄 수 있다. 라는 장점이 있다.(즉 필요한 정보가 객체 사이에 계약을 한다. => 복잡도를 낮춤)

추가적으로, 코드를 읽는 사람 입장에서도 인터페이스만 보고도 어떤 정보를 사용해야하는지 파악하기가 쉽다.라는 장점이 있을 것 같다.

++) 추가적인 장점이 있을 것 같은데.. 이는 이번 프로젝트에 적용을 해보면서 알아보려고 한다!(to be continue..)

실제로 사용한 예시를 봐보자

이 질문을 한 팀원이 일단은 사용을 하시고 질문을 해주셨다!
그래서 실제로 사용한 코드가 있으니! 그 코드를 통해 알아보도록 하자~

@Service
@RequiredArgsConstructor
@Transactional
public class UpdateUserNickNameService {

	private final UserRepository userRepository;

	private final ValidNicknameService validNicknameService;

	public UpdateNickNameResponse execute(UpdateUserNickNameRequest requestDto, Long userId) {
		validNicknameService.execute(requestDto);
        /**
        * 닉네임을 업데이트 하는 로직
        */
}

만약, 우리가 ValidNicknameService의 execute로 EmailSupplier를 받지 않고 Dto를 받았더라면 또 UpdateUserNickNameRequest를 통해 nickname을 검증하는 로직을 짰어야 할 것이다.

하지만, 우리가 EmailSupplier를 파라미터로 받는 덕분에
EmailSupplier를 implements한 ValidNicknameRequest, UpdateUserNickNameRequest를 모두 전달할 수 있다는 장점이 있다.

0개의 댓글