public class OrderServiceImpl implements OrderService{
// 구현 클래스에 의존한다 -> DIP 위반
// 1번
private final MemberRepository memberRepository = new MemoryMemberRepository();
// 2번
private final DiscountPolicy discountPolicy = new FixDiscountPolicy();
// 기능을 확장하여 변경하려면 클라이언트에 영향을 준다 -> OCP 위반
// 3번
private final DiscountPolicy discountPolicy = new RateDiscountPolicy();
}
이 코드를 보면 SOLID 원칙을 위반한 것을 볼 수 있다.
먼저 1, 2번 코드는 주문 서비스 클라이언트(OrderServiceImpl)가 인터페이스(MemberRepository)뿐만 아니라, 구현 클래스(MemoryMemberRepository)에 의존하고 있다. -> 따라서 의존관계 역전 원칙(DIP)를 위반하고 있다.
정액 할인에서 정률할인으로 기능을 확장하여 변경하기 위해서 2번 코드에서 3번 코드로 변경하였다. 이렇게 하면 클라이언트의 코드 변경이 일어나므로 클라이언트에게 영향을 준다. -> 따라서 개방 폐쇄 원칙(OCP)를 위반하고 있다.
그럼, SOLID 원칙을 지키기 위해서는 어떻게 해야 할까?
클라이언트의 구현 객체를 대신 생성하고 주입하는 역할을 하는 것이 필요하다!!
public class AppConfig {
public MemberService memberService(){
return new MemberServiceImpl(memberRepository());
}
public MemberRepository memberRepository(){
return new MemoryMemberRepository();
}
public OrderService orderService(){
return new OrderServiceImpl(memberRepository(), discountPolicy());
}
public DiscountPolicy discountPolicy(){
// return new FixDiscountPolicy();
return new RateDiscountPolicy();
}
}
AppConfig는 실제 동작에 필요한 구현 객체를 생성하고 연결하는 역할을 책임진다.
실제 동작에 필요한 MemberServiceImpl, MemoryMemberRepository, OrderServiceImpl, RateDiscountPolicy를 대신 생성해준다. 정액할인에서 정률할인으로 변경하고 싶을 때 AppConfig의 코드만 변경하면 되므로 클라이언트의 코드는 변경되지 않는다.
public class OrderServiceImpl implements OrderService{
private final MemberRepository memberRepository;
private final DiscountPolicy discountPolicy; // 인터페이스에만 의존하도록 변경 -> Null Pointer Exception 발생
public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
@Override
public Order createOrder(Long memberId, String itemName, int itemPrice) {
Member member = memberRepository.findById(memberId);
int discountPrice = discountPolicy.discount(member, itemPrice);
return new Order(memberId, itemName, itemPrice, discountPrice);
}
}
이렇게 생성자를 통해서 AppConfig가 대신 생성한 구현 객체를 주입(연결)해준다. 구현 객체를 생성하는 역할을 분리해주면 클라이언트가 인터페이스에만 의존하게 되므로 DIP를 지킬 수 있고, 기능이 변경될 때 클라이언트의 코드 변경은 일어나지 않아 OCP를 지킬 수 있다.
이것을 의존성 주입(DI)라고 하고 AppConfig처럼 객체를 생성하고 관리하면서 의존관계를 연결해주는 것을 DI 컨테이너라고 한다.
'Back-end > Spring' 카테고리의 다른 글
[Spring] 영속성 컨텍스트 (0) | 2022.01.26 |
---|---|
[SpringBoot] 컴포넌트 스캔과 의존관계 자동 주입 (@Component, @Autowired, @RequiredArgsConstructor) (0) | 2022.01.16 |
객체 지향 프로그래밍과 SOLID (0) | 2022.01.08 |
[SpringBoot] 10주차 스터디(AOP, 트랜잭션) (0) | 2021.10.23 |
[SpringBoot] 9주차 스터디 (페이징, JPA 연관관계) (0) | 2021.10.17 |