동기 본 글은 interface 란? spring 주입받아 사용하기 에서 이어집니다.
동적으로 구현체를 선택하는 방법을 알아보자
여러 판매자가 판매하는 상품을 장바구니에 담고 결제를 하였다고 가정해보자. 각 판매자에게 구입 요청을 해야할것이다.
이때 판매자에게 할수 있는 행위들이 interface로 정의되어있고, 실제로 요청해야하는 판매자에 따라서 구현체를 바꾸어야한다면?? 어떻게 될까? 예시를 확인해보자
ex)
1 2 3 라면 + 노트북을 장바구니에넣고 한번에 주문 -> 내부적으로는 오뚜기 / LG전자에 구매 요청을 해야야한다 주문, 주문취소, 환불이라는 행위자체는 동일하지만 요청해야하는 도메인정보등 세부정보는 다를것이다 그리고 이공통점을 판매자라는 인터페이스로, 달라지는 세부적인 부분을 오뚜기/LG전자라는 구현체로 구현했다면?
각 판매자에 해당하는 구현체를 선택하여 요청 로직을 실행해야할것이다.
인터페이스 구현 1 2 3 interface SampleInterface { ... }
1 2 3 4 @Repository public class SampleInterfaceImpl implements SampleInterface { ... }
1 2 3 4 @Repository public class SampleInterfaceOtherImpl implements SampleInterface { ... }
모든 인터페이스를 선언해놓고 분기처리하여 사용하기 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @Controller public class SampleController { @Autowired private SampleInterfaceImpl basic; @Autowired private SampleInterfaceOtherImpl other; @RequestMapping(path = "/path/Basic", method = RequestMethod.GET) public void basic () { basic.sampleMethod(); } @RequestMapping(path = "/path/Other", method = RequestMethod.GET) public void other () { other.sampleMethod(); } }
인터페이스를 Map에 넣어두고 꺼내서 사용하기 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 @Controller public class SampleController { @Autowired private SampleInterfaceImpl basic; @Autowired private SampleInterfaceOtherImpl other; Map<String, SampleInterface> services; @PostConstruct void init () { services = new HashMap ()<>; services.put("Basic" , basic); services.put("Other" , other); } @RequestMapping(path = "/path/{service}", method = RequestMethod.GET) public void method (@PathVariable("service") String service) { SampleInterface sample = services.get(service); sample.sampleMethod(); } }
ApplicationContext 를 활용하는 방법 구현체를 bean으로등록하고
1 2 3 4 5 6 7 8 9 10 11 12 13 @Component public class SampleInterfaceImpl implements SampleInterface { public void sampleMethod () { } } @Component public class SampleInterfaceOtherImpl implements SampleInterface { public void sampleMethod () { } }
ApplicationContext 의 getBean() 으로 구현체를 로딩하여 사용한다.
1 2 3 4 5 6 7 8 9 10 11 @Controller public class SampleController { @Autowired private ApplicationContext appContext; @RequestMapping(path = "/path/{service}", method = RequestMethod.GET) public void method (@PathVariable("service") String service) { SampleInterface sample = appContext.getBean(service, SampleInterface.class); sample.sampleMethod(); } }
Spring의 DI의 도움을 받는 방법 set이나list, map을 통해 자동으로 등록해준다
SampleInterface를 구현한 모든 인터페이스를 리스트에 주입
1 2 @Autowired private List<SampleInterface> SampleInterfaces;
SampleInterface를 구현한 모든 인터페이스를 맵에 주입한다. key는 구현체의 bean name.
1 2 @Autowired private Map<String, SampleInterface> SampleInterfaceMap;
그리고 사용하는곳에서는 맵에서 bean name을통해 선택하여 사용 할 수 있다.
1 2 3 4 5 6 7 8 9 10 11 12 13 void excuteSampleMethod () { String interfaceName = "" if (isFirst()) { interfaceName = "SampleInterfaceImpl" } else { interfaceName = "SampleInterfaceOtherImpl" } SampleInterface impl = SampleInterfaceMap.get(interfaceName); impl.sampleMethod(); }
enum으로 정책정하기 위처럼 분기처리하는대신 정책을 정리하는 enum을 두는것도 하나의 방법이다 먼저 동적으로 달라지는 종류를 enum으로 정의하고, 구현체의 이름을 적어준다
1 2 3 4 5 6 7 8 9 10 11 12 public enum SampleType { SAMPLE("SampleInterfaceImpl" ), OTHERS("SampleInterfaceOtherImpl" ) SampleType(String implementation) { this .implementation = implementation; } public String getImplementation () { return this .implementation; } }
이후 선택된 type에 맞게 구현체를 선택하여 기능을 수행한다.
1 2 3 4 5 void excuteSampleMethod (SampleType sampleType) { SampleInterface impl = SampleInterfaceMap.get(sampleType.getImplementation); impl.sampleMethod(); }
내가 선택한 방법 마지막에 소개한 Spring의 DI의 도움을 받는 방법
으로 구현하였다. 실제로는 로직이 필요하지는 않고 특정 데이터의 종류에 맞게 구현체를 선택만 하면 되는 스팩이었다 enum으로 정책을 한곳에서 관리해도되지만, 특정조건을 검사하는 책임
을 각 구현체들이
검사하는것이 더 맞다고 생각하였다.
각 구현체에서는 자신을 실행할수 있는지 여부를 검사하는 책임을 가진다
1 2 3 4 5 public class SampleInterfaceImpl implements SampleInterface { @Overide public boolean isAvailableType (SampleType) { } }
그리고 중간에서 중계해주는 Router
를 하나 만들어 주었다
1 2 3 4 5 6 7 8 9 10 11 12 @Component @RequiredArgsConstructor public class SampleInterfaceRouter { private final List<SampleInterface> sampleInterfaces; public SampleInterface getImplemetationByType (SampleType sampleType) { return sampleInterfaces.stream() .filter(e -> e.isAvailableType(sampleType)) .findFirst().orElseThrow(() -> new NotSupportedTypeException ()); } }
사용은 아래와같이 하면된다
1 2 3 4 5 6 7 8 @Autowired SampleInterfaceRouter sampleInterfaceRouter; void excuteSampleMethod (SampleType sampleType) { SampleInterface impl = sampleInterfaceRouter.getImplemetationByType(sampleType); impl.sampleMethod(); }
참고자료 https://stackoverflow.com/a/19027319 https://stackoverflow.com/a/37413949 https://stackoverflow.com/a/37408117 https://stackoverflow.com/q/10534053