DIP(Dependency Inversion Principle):애플리케이션의 주요 비즈니스 로직을 구현하는 서비스 객체(고수준 모듈)은 구현 객체(저수준 모듈)에 의존하지 않고 추상화된 인터페이스나 추상 클래스에 의존하도록 설계하는 원칙이다. 이로써 코드의 유연성과 재사용성을 높일 수 있다.
OCP(Open/Closed Principle):확장에는 열려 있고, 변경에는 닫혀 있어야 한다. 즉, 새로운 기능을 추가할 때 기존 코드를 수정하지 않고도 기능을 확장할 수 있도록 설계한다.
위반 예시:
주문 서비스 코드에서 인터페이스에 의존하므로 DIP는 지키고 있는 것처럼 보이지만, 기능이 확장되면 클라이언트 코드도 변경이 필요하기 때문에 OCP를 위반한다. 또한, 구체적인 구현 클래스에도 의존하고 있기 때문에 DIP도 위반하는 사례이다.
예를 들어, 새로운 할인 정책이나 결제 방식을 추가하려면, 클라이언트 코드에서 직접 구현 클래스의 변경이 필요하게 되므로 확장 시 기존 코드의 수정이 발생한다.
해결 방법:
외부에서 비즈니스 로직을 담당하는 클래스에 구현 객체를 주입하면 이러한 문제를 해결할 수 있다. 이를 통해 고수준 모듈이 저수준 모듈의 구현에 의존하지 않고, 추상화에만 의존하게 된다.
AppConfig: 구현 객체를 생성하고 연결하는 책임을 가지는 애플리케이션의 전체 동작 방식을 구성하는 별도의 설정 클래스로, 객체의 생성과 연결을 담당한다.
설정 파일(AppConfig)은 객체 생성과 연결을 수행하며, 비즈니스 로직을 담당하는 객체는 생성자를 통해 필요한 객체를 주입받아 실행한다. 이렇게 구현하면, 비즈니스 로직 객체는 구현 객체가 아닌 추상화에 의존하게 되어 DIP를 준수한다. 또한, 새로운 기능이 추가될 때 AppConfig에서만 코드를 수정하면 되므로 OCP도 준수할 수 있다.
결론:
이 방식으로 설계하면 비즈니스 로직 클래스는 구체적인 구현에 의존하지 않으므로 DIP를 지킬 수 있고, 기능 확장이 있을 때 설정 파일에서만 수정을 하면 되기 때문에 OCP도 위반하지 않는다.
해당 설정 클래스는 추후 Spring Boot의 @Configuration과 함수의 @Bean 애노테이션을 통해 쉽게 구현할 수 있다. @Configuration 애노테이션은 스프링 컨테이너에서 빈을 등록하는 설정 클래스임을 의미하며, @Bean 애노테이션은 스프링 컨테이너에 의해 관리되는 빈을 정의하는 데 사용된다.
이를 통해 구현 객체를 스프링 컨테이너가 관리하게 하고, 의존성 주입을 통해 코드의 결합도를 낮추며 확장성과 유지보수성을 더욱 높일 수 있다.
@Bean: 주로 설정 파일과 메서드 레벨에서 사용되며, 함수의 반환값을 스프링 컨테이너에 등록
@Component: 클래스 자체를 스프링 컨테이너에 등록
public class AppConfig {
@Bean
public OrderService orderService() {
return new OrderServiceImpl(discountPolicy());
}
@Bean
public DiscountPolicy discountPolicy() {
return new RateDiscountPolicy(); // 새로운 정책이 필요할 경우 이 부분만 수정
}
결과적으로, 스프링 프레임워크의 DI(Dependency Injection) 기능을 사용함으로써 DIP와 OCP 원칙을 보다 쉽게 적용할 수 있다.