spring boot/기술 적용

Spring boot에 전략패턴 적용

ballde 2022. 10. 10. 13:06

전략 패턴의 배경

예시 입니다 :)

인프런에 비즈니스 로그인(Account)이 있고 일반 로그인(User)이 있다.

public interface Login {
	public void login();
}
public class User implements Login {
	public void login() {
		System.out.println("일반 유저가 로그인 한다.")
	}
} 
public class Account implements Login {
	public void login() {
		System.out.println("비즈니스 유저가 로그인 한다.")
	}
} 

예시 만들기 어렵네..(좀 어거지임)

근데 여기서 login 메서드를 ‘일반 유저가 또 로그인을 한다.’ 라고 하고 싶을 경우 메서드를 직접 바꿔야함

⇒ 이것은 OCP 원칙을 어기는 것입니다..

⇒ 또한 wake() 메서드가 필요한 클래스들을 계속 생성해야해서 서비스가 커진다면 힘들어진다.

전략 패턴의 예시

그래서 전략 패턴은 위와 같은 login에 전략을 세워서 accountLogin, userLogin 등의 전략을 만들어서 끼워준다.

userType

@Getter
public enum UserType {

    USER("USER"), ACCOUNT("ACCOUNT"), ALL("ALL");

    final String type;

    UserType(String type) {
        this.type = type;
    }

}

controller

@RestController
@RequiredArgsConstructor
public class UserController {

    private final UserFactory userFactory;

    @PostMapping("api/v1/user")
    public void createUser(@RequestBody @Valid CreateUserRequest request) {
        UserFactoryService userService = userFactory.getUserService(request.getUserType());
        userService.createUser(request);
    }

}
  • request에서 userType을 받는다.
  • 받은 type을 통해서 userFactory.getUserService를 받아서 어떤 전략을 실행 시켜줄 지 정한다.

userFactoryService

public interface UserFactoryService {

    void createUser(CreateUserRequest request);

}
  • 비즈니스 유저, 일반 유저가 할 행동을 인터페이스로 적어줍니다.
  • 이 인터페이스를 implements 해서 구현해줍니다.
    • 만약 일반 유저, 비즈니스 유저 말고 또 다른 유저가 생기거나, 다른 전략이 생긴다면 기존 메서드를 변경하는 것이 아니라 인터페이스를 받아서 생성해준 후 바꿔 끼워주는 형태입니다.

userFactory

@Component
@RequiredArgsConstructor
public class UserFactory {

    private final UserService userService;
    private final AccountService accountService;
    private final Map<UserType, UserFactoryService> userServiceMap = new EnumMap<>(UserType.class);

    @PostConstruct
    void userServiceMap() {
        userServiceMap.put(UserType.USER, userService);
        userServiceMap.put(UserType.ACCOUNT, accountService);
    }

    public UserFactoryService getUserService(UserType userType) {
        UserFactoryService userFactoryService = userServiceMap.get(userType);
        if (userFactoryService == null) {
            throw new NotFoundException(String.format("존재하지 않는 %s 입니다.", userType));
        }
        return userFactoryService;
    }

}

UserService, accountService

@Service
@RequiredArgsConstructor
public class UserService implements UserFactoryService {

    @Override
    public void createUser(CreateUserRequest request) {
        // user 로직 수행
    }

}

@Service
@RequiredArgsConstructor
public class AccountService implements UserFactoryService {

    @Override
    public void createUser(CreateUserRequest request) {
        // account 로직 수행
    }

}

처음에는

public UserFactoryService getUserService(UserType userType) {
        UserFactoryService userFactoryService = userServiceMap.get(userType);
        if (userFactoryService == null) {
            throw new NotFoundException(String.format("존재하지 않는 %s 입니다.", userType));
        }
        return userFactoryService;
    }

이 부분을 처음에는 swich case 문으로 했었는데 나쁘지 않았지만 계속 추가해줘야 한다. 하지만 인프런 강의를 들으면서 Map을 사용하면 편리하다고 듣고 해봤는데

userServiceMap.put(UserType.ACCOUNT, accountService);

이 부분만 추가해주는 것이 나쁘지 않은것 같다.