1. Strategy 패턴 사용 배경 및 목적
Strategy 패턴은 코딩을 하다보면 자주 쓰이는 type별 if 분기구문을 정리하고 고도화하고 싶을 때 사용하기 좋다. 아래와 같은 코드를 예로 들 수 있다.
요청으로 들어오는 GrantType의 값에 따라 body 값이 형태가 조금씩 바뀌는 형태이다. 이런 경우 formatted 내부 메서드를 공통화하여 메서드로 만들 수 없으므로 if문으로 분기해놓은 것인데, 코드가 길고 유연성이 떨어진다.
String body;
String grantType = tokenPayload.getGrantType();
if(Objects.equals(grantType, GrantTypeEnum.AUTHORIZATION_CODE.getValue())) {
body = """
grant_type=%s&client_id=%s&client_secret=%s&code=%s&redirect_uri=%s
""".formatted(
tokenPayload.getGrantType(),
tokenPayload.getClientId(),
KeycloakClientInfo.getClientSecretBy(tokenPayload.getClientSecret()),
tokenPayload.getCode(),
tokenPayload.getRedirectUri());
} else if (Objects.equals(grantType, GrantTypeEnum.REFRESH_TOKEN.getValue())) {
body = """
grant_type=%s&client_id=%s&client_secret=%s&refresh_token=%s
""".formatted(
tokenPayload.getGrantType(),
tokenPayload.getClientId(),
KeycloakClientInfo.getClientSecretBy(tokenPayload.getClientId()),
tokenPayload.getRefreshToken()
);
} else {
//...
}
Strategy 패턴(전략 패턴)을 사용하면 아래와 같이 두 줄로 줄일 수 있다. 그리고 인터페이스화하여 코드가 분리되고 유연성이 증가한다. 물론 클래스 파일을 많이 만들고 구조가 복잡해지는 단점은 있다.
GrantTypeStrategy strategy = grantTypeStrategyFactory.getStrategy(GrantTypeEnum.valueOf(tokenPayload.getGrantType()));
String body = strategy.createRequestBody(tokenPayload);
2. 구체적인 구현 방법
Strategy 인터페이스
위 예시에서 타입별로 다른 body가 만들어져야하는 필요성이 있었다. 이런 타입별로 body를 만드는 Strategy 인터페이스를 하나 만들어준다.
public interface GrantTypeStrategy {
String createRequestBody(TokenPayload tokenPayload);
}
Strategy 구현체
그리고 타입별로 body가 달라져야하기 때문에 이를 모두 클래스화하고, GrantTypeStrategy를 상속하도록 한다. 나중에 해당 타입으로 묶어서 빈으로 사용하기 위해 Spring의 @Component 어노테이션도 달아준다.
@Component
public class AuthorizationCodeGrantStrategy implements GrantTypeStrategy {
@Override
public String createRequestBody(TokenPayload tokenPayload) {
return """
grant_type=%s&client_id=%s&client_secret=%s&code=%s&redirect_uri=%s
""".formatted(
tokenPayload.getGrantType(),
tokenPayload.getClientId(),
KeycloakClientInfo.getClientSecretBy(tokenPayload.getClientSecret()),
tokenPayload.getCode(),
tokenPayload.getRedirectUri()
);
}
@Override
public GrantTypeEnum getGrantType() {
return GrantTypeEnum.AUTHORIZATION_CODE;
}
}
@Component
public class RefreshTokenGrantStrategy implements GrantTypeStrategy {
@Override
public String createRequestBody(TokenPayload tokenPayload) {
return """
grant_type=%s&client_id=%s&client_secret=%s&refresh_token=%s
""".formatted(
tokenPayload.getGrantType(),
tokenPayload.getClientId(),
KeycloakClientInfo.getClientSecretBy(tokenPayload.getClientId()),
tokenPayload.getRefreshToken()
);
}
@Override
public GrantTypeEnum getGrantType() {
return GrantTypeEnum.REFRESH_TOKEN;
}
}
Factory
그리고 조건에 따라 이런 Strategy 구현체들을 생성할 수 있는 Factory 클래스를 만들어준다.
Spring 다형성 🌟
여기서 살펴볼 점은 List<GrantTypeStrategy> 형태로 해당 인터페이스에 속하는 모든 구현체를 다형성을 활용해 불러올 수 있다는 점이다. 평소에 빈들을 주입하듯이 @RequiredArgsConstructor를 선언해주면 인터페이스 타입의 List를 선언해주는 것만으로 모든 구현체를 불러와서 로직을 처리할 수 있어 편하다.
@Component
@RequiredArgsConstructor
public class GrantTypeStrategyFactory {
private final List<GrantTypeStrategy> strategies;
public GrantTypeStrategy getStrategy(GrantTypeEnum grantTypeEnum) {
return strategies.stream()
.filter(strategy -> strategy.checkType(grantTypeEnum))
.findFirst()
.orElseThrow(() -> new IllegalArgumentException("Invalid grant type: " + grantTypeEnum.getValue()));
}
}
Default 메서드 선언
부가적으로, strategy.checkType()이라는 메서드를 썼는데 각 Strategy 구현체가 아니라 interface에 default 메서드로 선언함으로써 각 구현체가 똑같은 메서드를 참조할 수 있도록 해주었다.
public interface GrantTypeStrategy {
String createRequestBody(TokenPayload tokenPayload);
GrantTypeEnum getGrantType();
default Boolean checkType(GrantTypeEnum grantType) {
return this.getGrantType().equals(grantType);
}
}
이제 맨 처음에 본 것과 같이 Factory와 각 구현체의 메서드를 사용하여 처리하면 코드가 훨씬 간결해진다. 나중에 새로운 GrantType이 생성되더라도 GrantTypeStrategy로 묶어 응집력있게 관리할 수 있고 유연하게 대처할 수 있다.
GrantTypeStrategy strategy = grantTypeStrategyFactory.getStrategy(GrantTypeEnum.valueOf(tokenPayload.getGrantType()));
String body = strategy.createRequestBody(tokenPayload);
'Programming-[Base] > Design, Architecture' 카테고리의 다른 글
[작성중][헥사고날 아키텍처] - 1. 기본 개념, 도메인 헥사곤 (0) | 2024.04.09 |
---|---|
[작성중][잠정 중단] 2. Bridge - 예제 : 구체적 이해 (0) | 2021.12.01 |
1. Bridge - 개념 : 기본 이해 (0) | 2021.11.24 |