개인 공부용/crud.jpa.api

템플릿 메서드 패턴

pon9 2024. 12. 15. 14:57

현재 코드의 문제 : 같은 로직(validateUser)가 모든 crud 메서드에 중복 구현되어있다.

위: 로그인 사용자용 createPost 메서드

아래: 비로그인 사용자용 create, updatePost 메서드

 

user검증로직이 중복되며, 캡슐화 하는것이 좋다.

 

해당문제를 해결하기 위한 "템플릿 메서드 패턴" 을 적용시켜 보았다.

https://refactoring.guru/ko/design-patterns/template-method

 

템플릿 메서드 패턴

/ 디자인 패턴들 / 행동 패턴 템플릿 메서드 패턴 다음 이름으로도 불립니다: Template Method 의도 템플릿 메서드는 부모 클래스에서 알고리즘의 골격을 정의하지만, 해당 알고리즘의 구조를 변경하

refactoring.guru

요즘 자기 전에 보는 사이트다. 디자인 패턴에 관심이 매우 많다. 코드를 리팩토링 하는 것이 정말 즐겁다 ㅎㅎ

 

 

템플릿 메서드 패턴이란?

 

중복되는 공통 알고리즘(내 코드에서는 user검증 로직) 을 템플릿 메서드로 정의한다.

세부 단계는 추상메서드나 구현메서드로 정의되어서 하위 클래스에서 오버라이드 하거나 그대로 사용 가능하다.

알고리즘의 구조는 변경되지 않고, 일부 단계만 커스터마이징 할 수 있도록 한다.

 

즉, 템플릿을 상위 클래스가 제공하고 하위 클래스는 그 틀에 맞게 구체적인 구현을 추가하는 방식이다.

 

템플릿 메서드 패턴의 구조는 두 부분으로 나뉜다.

추상 클래스: 공통 알고리즘의 전체 흐름(템플릿 메서드)를 정의한다. 알고리즘의 각 단계는, 추상 메서드 또는 구체 메서드로 정의된다.

하위 클래스: 추상 클래스에서 정의된 추상 메서드를 구현한다. 필요에 따라서, 상위 클래스의 일부 메서드를 오버라이드 할 수 있다.

 

중복 코드 제거, 유지보수가 쉬움, 일관성이 유지된다는 장점을 가지고 있지만,

하위 클래스가 증가하면 관리가 복잡해질 수 있고, 흐름이 고정되기 때문에 일부 커스터마이징이 어려울 수 있다.

 

 

내 코드에 적용 예시

public abstract class AbstractBoardService {

    //템플릿 메서드로 전체 흐름을 정의.
    public final BoardResponseDto createPost(
    	BoardRequestDto dto, 
        Object userInfo){
        validateUser(userInfo);	//사용자 검증
        return executeCreatePost(dto, userInfo);	//게시글 생성로직
    }

    public final BoardResponseDto updatePost(
    	BoardRequestDto dto, 
    	Object userInfo, 
    	Long id){
        validateUser(userInfo);	//사용자 검증
        return executeUpdatePost(dto, userInfo, id);	//게시글 수정로직
    }

    public final void deletePost(
    	Object userInfo, 
        Long id){
        validateUserForDelete(userInfo, id);	//사용자 검증
        executeDeletePost(userInfo, id);	//게시글 삭제로직
    }

    //공통 알고리즘: 하위클래스에서 반드시 구현해야 함.
    protected abstract void validateUser(Object userInfo);
    protected abstract void validateUserForDelete(Object userInfo, Long id);
    protected abstract BoardResponseDto executeCreatePost(
    	BoardRequestDto dto, 
    	Object userInfo);
    protected abstract BoardResponseDto executeUpdatePost(
    	BoardRequestDto dto, 
    	Object userInfo, Long id);
    protected abstract void executeDeletePost(Object userInfo, Long id);
}
@Service
public class BoardService extends AbstractBoardService{

    @Override
    protected void validateUser(Object userInfo){
        HttpServletRequest req = (HttpServletRequest) userInfo;
        userValidationService.validateUser(req);
    }

    @Override
    protected void validateUserForDelete(Object userInfo, Long id){
        boardValidationService.validateBoard(id);

        HttpServletRequest req = (HttpServletRequest) userInfo;
        userValidationService.validateUser(req);
    }

    @Override
    protected BoardResponseDto executeCreatePost(
    	BoardRequestDto dto,
    	Object userInfo){
        //생성 로직
    }

    @Override
    protected BoardResponseDto executeUpdatePost(
    	BoardRequestDto dto, 
    	Object userInfo, 
    	Long id){
        //수정 로직
    }

    @Override
    protected void executeDeletePost(
    	Object userInfo, 
    	Long id){
        //삭제 로직
    }
@Service
//익명 사용자 서비스
public class BoardAnonymousService extends AbstractBoardService{

    private final PasswordEncoder passwordEncoder;
    private final BoardValidationService boardValidationService;

    @Override
    protected void validateUser(Object userInfo){
        BoardRequestDto dto = (BoardRequestDto) userInfo;
        boardValidationService.validateAnonymousUser(
        	dto.getNickname(), 
        	dto.getPassword());
    }

    @Override
    protected void validateUserForDelete(Object userInfo, Long id){
        boardValidationService.validateBoard(id);

        BoardPasswordRequestDto dto = (BoardPasswordRequestDto) userInfo;
        boardValidationService.validateBoardPassword(
        	id, 
            dto.getPassword());
    }

    @Override
    protected BoardResponseDto executeCreatePost(
   		BoardRequestDto dto, 
    	Object userInfo){
        //생성 로직
    }

    @Override
    protected BoardResponseDto executeUpdatePost(
    	BoardRequestDto dto, 
        Object userInfo, 
        Long id){
        //수정 로직
    }

    @Override
    protected void executeDeletePost(
    	Object userInfo, 
    	Long id){
        //삭제 로직
    }

 

중복되는 유저 검증 로직을 추상 클래스에서 정의하고 그 템플릿에 맞춰서, 하위 클래스에서 메서드를 구현하였다.

로그인 사용자는 HttpServletRequest로, 익명 사용자는 dto로 userInfo를 나타낸다.

이제 로그인 사용자와 익명 사용자의 validate로직은 동일한 템플릿 메서드로 일관성을 지닌다!