[스프링&JPA 스터디] #2 기존 예제의 문제점, AppConfig, 리팩터링, 정책 변경

오예찬·2023년 7월 11일
0

spring&jpa 스터디

목록 보기
2/15

기존 예제의 문제점


public class OrderServiceImpl implements OrderService {

 private final DiscountPolicy discountPolicy = new FixDiscountPolicy();
 private final DiscountPolicy discountPolicy = new RateDiscountPolicy();
 
}

OCP, DIP 같은 객체지향 설계 원칙을 충실히 준수했는가?

  • DIP: 주문서비스 클라이언트 OrderServiceImplDiscountPolicy 인터페이스에 의존하면서 DIP를 지킨 것 같지만, 클래스 의존관계를 분석해 보면 추상(인터페이스) 뿐만 아니라 구체(구현) 클래스에도 의존하고 있다.

    • 추상(인터페이스) 의존: DiscountPolicy

    • 구체(구현) 클래스: FixDiscountPolicy,RateDiscountPolicy


  • OCP: 현재 코드는 기능을 확장해서 변경하면, 클라이언트 코드에 영향을 준다. 따라서 OCP를 위반한다.

기대했던 의존 관계

실제 의존관계



AppConfig


  • DIP를 위반하지 않도록 인터페이스에만 의존하도록 의존관계를 변경하면 된다.
public class OrderServiceImpl implements OrderService {

 //private final DiscountPolicy discountPolicy = new RateDiscountPolicy();
 private DiscountPolicy discountPolicy;
 
}

위 코드의 문제점

  • 구현체가 없기 때문에 그냥 실행할 경우 NPE(null pointer exception)가 발생한다.

  • 이 문제를 해결하려면 누군가가 클라이언트인 OrderServiceImplDiscountPolicy의 구현 객체를 대신 생성하고 주입해주어야 한다. -> AppConfig의 등장

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


public class AppConfig {

 public MemberService memberService() {
 return new MemberServiceImpl(new MemoryMemberRepository());
 }
 
 public OrderService orderService() {
 return new OrderServiceImpl(new MemoryMemberRepository(),new FixDiscountPolicy());
 }
 
}

  • AppConfig는 애플리케이션의 실제 동작에 필요한 구현 객체를 생성한다.
    MemberServiceImpl
    MemoryMemberRepository
    OrderServiceImpl
    FixDiscountPolicy

  • AppConfig는 생성한 객체 인스턴스의 참조(레퍼런스)를 생성자를 통해서 주입(연결)해준다.
    MemberServiceImpl -> MemoryMemberRepository
    OrderServiceImpl -> MemoryMemberRepository , FixDiscountPolic

클래스 다이어그램

  • 객체의 생성과 연결은 AppConfig 가 담당한다.

  • DIP 완성: MemberServiceImplMemberRepository 인 추상에만 의존하면 된다. 이제 구체 클래스를 몰라도 된다.

  • 관심사의 분리: 객체를 생성하고 연결하는 역할과 실행하는 역할이 명확히 분리되었다.

회원 객체 인스턴스 다이어그램

  • appConfig 객체는 memoryMemberRepository 객체를 생성하고 그 참조값을 memberServiceImpl 을 생성하면서 생성자로 전달한다.

  • 클라이언트인 memberServiceImpl 입장에서 보면 의존관계를 마치 외부에서 주입해주는 것 같다고 해서 DI(Dependency Injection) 우리말로 의존관계 주입 또는 의존성 주입이라 한다.


AppConfig 리팩터링

현재 AppConfig를 보면 중복이 있고, 역할에 따른 구현이 잘 안보인다.

기대하는 그림

리팩터링 전

public class AppConfig {

 	public MemberService memberService() {
 		return new MemberServiceImpl(new MemoryMemberRepository());
 	}
    
 	public OrderService orderService() {
 		return new OrderServiceImpl(new MemoryMemberRepository(),newFixDiscountPolicy());
 	}
}
  • 중복을 제거하고, 역할에 따른 구현이 보이도록 리팩터링 해야 한다.

리팩터링 후

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();
   }
}
  • new MemoryMemberRepository() 이 부분이 중복 제거되었다. 이제 MemoryMemberRepository 를 다른 구현체로 변경할 때 한 부분만 변경하면 된다.

  • AppConfig 를 보면 역할과 구현 클래스가 한눈에 들어온다. 애플리케이션 전체 구성이 어떻게 되어있는지 빠르게 파악할 수 있다.

정책 변경

  • 이제 할인 정책을 변경해도, 애플리케이션의 구성 역할을 담당하는 AppComfig만 변경하면 된다. 클라이언트 코드인 OrderServiceImpl를 포함해서 사용 영역의 어떤 코드도 변경할 필요가 없다.

  • 구성 영역은 당연히 변경된다. 구성 역할을 담당하는 AppConfig를 애플리케이션이라는 공연의 기획자로 생각하자. 공연 기획자는 공연 참여자인 구현 객체들을 모두 알아야 한다.

  • FixDiscountPolicy -> RateDiscountPolicy로 변경해도 구성 영역만 영향을 받고, 사용 영역은 전혀 영향을 받지 않는다.

참고

스프링 핵심 원리 - 기본편 (김영한)

profile
개발자를 그만두기 전 알아야 하는 100가지 이야기

0개의 댓글