객체 지향 설계에 대해 공부하던 중 궁금한 점이 하나 생겼다.
지금까지 MVC 패턴으로 설계할때 Controller와 Service는 인터페이스 상속을 받지 않고 1:1 관계로 의존 관계를 맺어왔다.
하지만 레퍼런스를 참고하던 중 Service, Repository를 인터페이스화 하고 Serviceimpl, Repositoryimpl로 구현체를 만들어 사용하는 것을 보았고, 1:N관계라면 이해가 가지만 1:1관계에서 굳이 필요한가? 객체지향적 설계에 맞게하기위해 억지로 쓰는 것일까? 궁금증이 생겼다.
위 구조처럼 인터페이스를 상속받은 Service구현체가 따로 있다. 보통 인터페이스를 사용하는 이유는 객체지향 특징 중 하나인 다형성을 가지게 하기 위함으로 알고있다.
다형성 : 하나의 자료형에 여러가지 객체를 대입하여 다양한 결과를 얻어내는 성질
OCP 원칙에 맞는 설계이긴 하지만 굳이 관계를 하나 더 만들어서 사용해야할까?
이에 관련해서 구글링을 해봤고, 이해하기에 좋은 내용을 찾았다.
public interface IService {
String test();
}
구현체
@Service
public class AServiceImpl implements IService {
@Override
public String test() {
return "AServiceImpl";
}
}
컨트롤러
// Controller
@RequiredArgsConstructor
@RestController
public class SampleController {
private final IService service;
@GetMapping("/test")
public String test(){
return service.test();
}
}
위 예시 코드를 보면 컨트롤러는 특정 구현체가 아닌 IService 타입을 의존 주입받고 있다. (물론 특정 구현체인 AServiceImpl 을 의존주입 받아도 괜찮다.)
이렇게 구현체가 아닌 인터페이스를 의존받으면 OCP 원칙을 지킬 수 있다. 무슨 뜻인지 잘 모르겠다면 임의로 BServiceImpl 구현체를 추가해보자.
또 다른 구현체(BServiceImpl)public class BServiceImpl implements IService {
@Override
public String test() {
return "BServiceImpl";
}
}
AServiceImpl 와 마찬가지로 IService에 선언된 test() 메서드를 구현하고 있다. 이제 이 클래스에 @Service 어노테이션을 붙이면 스프링 컨테이너에 의해 SampleController 로 의존주입이 된다.
(AServiceImpl 의 @Service 어노테이션은 제거할 것)
IService 라는 인터페이스를 구체화 하는 구현체간의(AServiceImpl, BServiceImpl) 차이만 있을 뿐, 클라이언트 코드(컨트롤러) 입장에서는 무엇이 변했는지 전혀 모르는 상태이다. 여기서 모르는 상태라는 것은, ServiceLayer 의 구현체가 변경되더라도 클라이언트 코드는 전혀 수정할 필요가 없다는 의미.
지금은 단순히 test() 라는 메서드에서 문자열을 출력하는 기능밖에 없어 감흥이 없을 수 있다. 유명한 예제인 정률 할일(%할인) 정액 할인(1000원 할인) 을 가정해보면, 만약 현재는 정률 할인을 진행하더라도, 갑자기 정액할인으로 정책이 변경된다면, 위의 예제처럼 서비스레이어의 어노테이션만 바꿔주면 간단하게 변경된 정책을 코드상 배포할 수 있게 된다.
AServiceImpl 의 @Service 어노테이션을 제거하지 않고 BServiceImpl에도 @Service가 있으면 어떻게되는건가요?