본문 바로가기

프로그래밍/spring

[스프링 기본] 객체 지향 원리 적용

시작에 앞서 이 글은 인프런 김영한 님의 강의를 정리하며 쓴 글입니다.

웹 백엔드를 공부하시는 분이라면 꼭 김영한님의 강의를 들어보시길 추천합니다.

 

객체 지향의 원리를 더 적용해보자!

 

 

강의에서는 스프링 핵심 원리 - 예제 만들기 를 통해서 순수한 자바 코드를 통해 객체 지향의 기본 개념과 다형성, 인터페이스, 그리고 SOLID 원칙을 어떻게 적용하는지 살펴봤습니다. 이번에는 조금 더 발전시켜서 새로운 요구사항에 대응하고, 스프링 컨테이너에서 동작하도록 만들어보겠습니다.

 

 

 

새로운 할인 정책 개발

제작사에서 새로운 요구사항이 들어왔습니다. 할인 정책을 고정 금액이 아닌 주문 금액의 일정 비율로 변경하고 싶다는 것이죠.

이를 수용하기 위해 RateDiscountPolicy라는 새로운 할인 정책 클래스를 개발했습니다. 주문 금액에 따른 할인을 적용할 수 있도록 설계되었습니다. 테스트 코드를 통해 새로운 정책이 제대로 동작하는지 확인하고 있습니다.

 

 

 

 

새로운 할인 정책 적용과 문제점

 

그런데 문제가 발생했습니다. 기존 코드에서는 다형성, 인터페이스, SOLID 원칙을 준수하는 것으로 보이지만 실제로는 문제가 있었습니다. OrderServiceImpl이 DiscountPolicy 인터페이스와 함께 구체 클래스인 FixDiscountPolicy에 의존하고 있어서 DIP 원칙을 위반하고 있었습니다. 또한, 구체 클래스에도 의존하고 있어 OCP 원칙을 위반하게 되는데요.

구현체도 의존하고 인터페이스도 의존해서 이도저도 아닌 상황이 되는것입니다.

 

public class OrderServiceImpl implements OrderService {
     private final MemberRepository memberRepository = new
 MemoryMemberRepository();
     private final DiscountPolicy discountPolicy = new FixDiscountPolicy(); //여기
     @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);
} }

 

실제 의존 관계

 

 

 

해결 방안 - DIP와 OCP 준수

DIP를 위반하지 않도록 인터페이스에만 의존하도록 의존관계를 변경하면 됩니다.

이 문제를 해결하려면 누군가가 클라이언트인 `OrderServiceImpl` 에 `DiscountPolicy` 의 구현 객체를 대신 생성하고 주입해주어야 합니다.

 

 

 

 

 

 

관심사의 분리

로미오와 줄리엣 공연에서 배우가 여러 역할을 동시에 수행하고 있어서 문제가 발생하고 있었습니다. 이를 해결하기 위해 AppConfig라는 공연 기획자를 도입했습니다. 이제 구체 클래스의 선택, 객체 생성, 주입 등의 역할을 AppConfig가 담당하고, 클라이언트 코드는 해당 기능을 실행하는 역할로 단순화되었습니다.

 

 

애플리케이션의 전체 동작 방식을 구성(config)하기 위해, 구현 객체를 생성하고, 연결하는 책임을 가지는 별도의 설정 클래스를 만드는 것입니다.

 

 

 

 

public class AppConfig {
     public MemberService memberService() {
         return new MemberServiceImpl(memberRepository());
}
     public OrderService orderService() {
         return new OrderServiceImpl(
                 memberRepository(),
                 discountPolicy());
}
     public MemberRepository memberRepository() {
         return new MemoryMemberRepository();
}
     public DiscountPolicy discountPolicy() {
         return new FixDiscountPolicy();
}
}
```
public class MemberServiceImpl implements MemberService {
     private final MemberRepository memberRepository;
     public MemberServiceImpl(MemberRepository memberRepository) {
         this.memberRepository = memberRepository;
}
     public void join(Member member) {
         memberRepository.save(member);
}
     public Member findMember(Long memberId) {
         return memberRepository.findById(memberId);
} }

 

MemberServiceImpl` 입장에서 생성자를 통해 어떤 구현 객체가 들어올지(주입될지)는 알 수 없습니다.

MemberServiceImpl` 의 생성자를 통해서 어떤 구현 객체를 주입할지는 오직 외부( `AppConfig` )에서 결정됩니다.

 

 

 

새로운 할인정책 적용

 

새로운 구조와 할인 정책을 적용해보겠습니다. 이번에는 정액 할인 정책(FixDiscountPolicy)을 정률 % 할인 정책(RateDiscountPolicy)으로 변경하는 작업입니다.

먼저, RateDiscountPolicy 클래스를 구현하고 해당 정책을 적용해보겠습니다. 아래는 예제 코드입니다.

public class AppConfig {
     public MemberService memberService() {
         return new MemberServiceImpl(memberRepository());
}
     public OrderService orderService() {
         return new OrderServiceImpl(
                 memberRepository(),
                 discountPolicy());
}
     public MemberRepository memberRepository() {
         return new MemoryMemberRepository();
}
     public DiscountPolicy discountPolicy() {
         //return new FixDiscountPolicy();
         return new RateDiscountPolicy();
}
}
```

 

 

이렇게 변경하면 AppConfig에서 할인 정책을 RateDiscountPolicy로 변경함으로써 쉽게 새로운 정책으로 전환할 수 있습니다.