대부분의 프로젝트는 Service를 만들 때 MemberService와 같이 서비스를 인터페이스로 설계하고, MemberServiceImpl이라는 구현체인 클래스를 생성해서 사용하는 방식으로 설계된다.
여태 프로젝트를 하면서 그런가보다 하면서 구현했는데, 객체 지향을 공부하면서 문득 내가 사용했던 ServiceImpl은 왜 썼던 걸까? 궁금해져서 포스팅하게 되었다.
이론상으로 인터페이스와 구현체를
분리함
으로써 구현체를 독립적으로 확장할 수 있고, 구현체 클래스를 변경하거나 확장해도 이를 사용하는 클라이언트 코드에 영향을 주지 않기 위함이다.
이처럼 추상화를 통한 구현 방식은 객체지향의 특징인다형성
과OCP
에 연관이 되어있다.
인터페이스
public interface IService { String test(); }
구현체
@Service public class AServiceImpl implements IService { @Override public String test() { return "AServiceImpl"; } }
컨트롤러
@RequiredArgsConstructor @RestController public class SampleController { private final IService service; @GetMapping("/test") public String test(){ return service.test(); } }
그런데 우리가 보통 spring 코드를 짤 때 인터페이스 하나당 구현체 하나와 연결이 되어있다. 1:N관계가 아니라 1:1관계를 형성하면서 인터페이스를 사용하고 있다는 것이다..
너무 관습적인 추상화를 구현하고 있는 것이 아닌가 생각이 든다.
- 분리 설계를 만족시키기 위해 불필요한 네이밍을 반복할 필요가 없음
- 클래스와 인터페이스 간의 상호작용을 걱정할 필요가 없음
- 의존성 감소
- 단위 테스트를 하기 위해 service 인터페이스와 serviceImpl 구현체를 모두 생성하지 않아도 됨
결국 인터페이스와 구현체가 1:N이 아니라 1:1 관계라면 때문에 서비스 코드만 존재해도 괜찮지 않을까?
사실 정답은 없다
프로젝트를 설계하는 사람의 마음이다...
그래도 필자는 계속 분리 패턴을 이용할 것이다.
이 구조는 OOP 관점으로 봤을 때 다형성 혹은 개방 폐쇄 원칙(OCP)때문에 사용한다.
하나의 인터페이스에 여러 구현체를 생성하게 하며,
API를 구조할 때 기획 정책은 얼마든지 변경될 수 있다고 느꼈고, 인터페이스는 그대로 두고 구현체를 변경하며 추가하여 OCP를 지키게 할 것이다.