개발을 하다보면 로직을 추상화한 뒤, 특정 타입에 따라 전략패턴
을 적용하고 해당 타입에 맞는 서비스 객체를 이용하는 로직을 짜는 경우가 많다.
개발자마다 구현하는 방법은 여러가지가 있겠지만, 이전 글인 [스프링] 일반 클래스에서 Bean 객체에 접근하기 + Environment 를 이용해 property 쉽게 가져오기 (v2) 를 이용하여 구현하는 방법을 공유해보고자 한다.
Tech Stack
JDK 8
Spring boot 2.5.0
SocialMedia 라는 Interface 를 구현하는 클래스
이 있다.
SocialMedia.java
public interface SocialMedia {
String post(String content);
}
Facebook.java
@Slf4j
@Service
public class Facebook implements SocialMedia{
@Override
public String post(String content) {
log.info("This is a post for Facebook, :: {}", content);
return "Posted on Facebook : " + content;
}
}
Instagram.java
@Slf4j
@Service
public class Instagram implements SocialMedia{
@Override
public String post(String content) {
log.info("This is a post for Instagram, :: {}", content);
return "Posted on Instagram : " + content;
}
}
위의 클래스들을 요청하는 type 에 맞추어 Enum, BeanContext, ServiceProvider 로 간단히 구현해보고자 한다.
SocialMediaType.java
public enum SocialMediaType {
FACEBOOK(Facebook.class),
INSTAGRAM(Instagram.class),
;
@Getter
private final Class<? extends SocialMedia> clazz;
SocialMediaType(Class<? extends SocialMedia> clazz) {
this.clazz = clazz;
}
}
SocialMedia 에 맞는 Enum 타입을 선언해주고, 멤버변수로는 SocialMedia 인터페이스를 구현한 클래스를 지정해준다.
SocialMediaServiceProvider.java
public class SocialMediaServiceProvider {
private static Map<String, Class<? extends SocialMedia>> socialMediaMap = new HashMap<>();
static {
for (SocialMediaType value : SocialMediaType.values()) {
socialMediaMap.put(value.toString().toLowerCase(), value.getClazz());
}
}
public static SocialMedia service(String socialMediaType){
Class<? extends SocialMedia> serviceClass = socialMediaMap.get(socialMediaType.toLowerCase());
if (serviceClass != null){
return BeanContext.get(serviceClass);
}
throw new IllegalArgumentException("No proper type for the given media type : " + socialMediaType);
}
}
SocialMediaServiceProvider 클래스는 외부에서 온 socialMediaType :String
에 맞게 서비스 객체
를 제공해주는 역할을 한다. ( BeanContext 클래스는 이전 글 참조)
SocialMediaController.java
@Slf4j
@RestController
@RequestMapping("/api/v1/social-media")
public class SocialMediaController {
@GetMapping("/{type}")
public String post(@PathVariable String type, @RequestParam String content){
log.info("The content of the post : {}", content);
SocialMedia service = SocialMediaServiceProvider.service(type);
return service.post(content);
}
}
type 을 기반으로 SocialMediaService 객체를 반환 받은 뒤 post(content) 메소드를 수행한다. (RequestBody 로 받는 것이 맞으나, 편의상 requestParam 으로 전달했다.)
[GET] http://localhost:8080/api/v1/social-media/instagram?content=인스타사진올리기
[GET] http://localhost:8080/api/v1/social-media/tiktok?content=잘못된요청
회사에서 이에 대한 코드를 구현한 것을 보고 조금 더 나은 방법이 있지 않을까 하는 생각에서 만들어본 코드인데 Provider
는 우선 디자인 패턴이 아니고 안티 패턴이라는 말도 있다.
또한 Provider
는 Factory
패턴처럼 새로운 객체를 반환하는 클래스인 것으로 알고 있는데, 필자는 프로바이더라는 것에 대해 정확히 알고 구현한 것이 아니어서, 조금 더 살펴볼 필요가 있어보인다.
이번 포스트는 디자인 패턴에 대해 고찰/응용해본 것은 아니지만 그래도 디자인 패턴에 대해 생각해보자면, 디자인 패턴은 수학으로 치면 공식과도 같은 느낌이다. 공식을 몰라도 문제를 풀 수는 있지만, 알면 조금 더 쉽고, 우아하게 풀 수 있듯 프로그래밍 세계에서도 디자인 패턴을 알고 적용했을 때 특정 문제를 좀 더 수월하게 해결할 수 있을 수 있고, 후에 유지보수 / 확장성 차원에서도 많은 이점을 가져다주기 때문에 조만간 날 잡고 디자인 패턴에 대해서 깊게 학습해야 할것 같다.