jhhan의 블로그

자바와 스프링 그 사이(3) 본문

JAVA

자바와 스프링 그 사이(3)

jhhan000 2020. 12. 29. 13:45

2편에 이어서 진행합니다.

 

저번에 DIP를 지켜보려고 노력하다가 에러가 뜨는 것을 마지막으로 마무리 했습니다.

이번에는 그것을 한 번 고쳐보도록 하죠.

 

이전의 코드는 

  • 인터페이스 A 존재
  • A를 구현한 구현체 A'이 있다.
  • 인터페이스 B 존재
  • B를 구현한 구현체 B'이 있다.
  • 원래라면 A'과 B'은 별개이다. 그래서 서로 관여할 일이 없다.
  • 하지만 현재 A'이 B'을 직접 선택하는 것 과 같습니다.

이전 포스트에 적은 제 글을 한 번 보겠습니다.

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);
    }
}
  • 인터페이스 A -> OrderService
  • 구현체 A' -> OrderServiceImpl
  • 인터페이스 B -> DiscountPolicy
  • 구현체 B' -> FixDiscountPolicy
  • 위의 코드에서 OrderServiceImpl이 FixDiscountPolicy를 직접 선택해서 사용하고 있습니다.
  • DIP가 지켜지지 않은 것을 알 수 있습니다.
    (MemberRepository 역시 그렇다는 것을 눈치챌 수 있습니다.)

이런 식으로 계속 작성한다면 DIP를 지키는게 힘듭니다.

그래서 구현 객체 생성과 연결을 하는 다른 클래스가 필요합니다.

위의 구조처럼 AppConfig라는 클래스를 추가해줍니다.

public class AppConfig {

    /**
     *  MemberService가 호출되면?
     *  그러면 MemberServiceImpl이 생성이 됨
     *  그 때 MemoryMemberRepository가 주입이 됨
     */
    public MemberService memberService() {
        return new MemberServiceImpl(new MemoryMemberRepository());
    }

	/**
     *  OrderService가 호출되면?
     *  그러면 OrderServiceImpl이 생성이 됨
     *  그 때 MemoryMemberRepository와 FixDiscountPolicy가 주입이 됨
     */
    public OrderService orderService() {
        return new OrderServiceImpl(new MemoryMemberRepository(), new FixDiscountPolicy());
    }
}

주석이 추가되어서 그렇지 생각외로 간단한 코드입니다.

이제 이 클래스에서 어떤 구현 객체를 선택할지 정할 수 있습니다.

그리고 이렇게 코드를 바꾼다면 아마도 오류가 날 것입니다.

이제 MemberServiceImpl과 OrderServiceImpl을 바꿔보겠습니다.

public class MemberServiceImpl implements MemberService{

    private final MemberRepository memberRepository;

    public MemberServiceImpl(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }

	...
}
public class OrderServiceImpl implements OrderService{

    private final MemberRepository memberRepository;
    private final DiscountPolicy discountPolicy;

    public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
        this.memberRepository = memberRepository;
        this.discountPolicy = discountPolicy;
    }

	...
}

(MemberServiceImpl에 대해서만 설명하겠습니다...)

  • 초반의 코드들이 이렇게 바뀝니다.
  • 그리고 AppConfig에서 정해진 구현 객체를 받아오기 위해 MemberServiceImpl에 생성자를 만들었습니다.
  • 이렇게 되면 interface에만 의존하고 코드를 진행할 수 있습니다. (구현체를 특정하지 않아도 됩니다.)
  • -> MemberServiceImpl은 MemberRepository라는 인터페이스에만 의존하면 됩니다. : DIP 조건 성립
  • MemberServiceImpl의 입장에서 본다면 의존관계가 외부에서 주입이 되는 것처럼 보입니다.
    -> 이를 DI(Dependency Injection)라고 합니다. 의존성 주입 & 의존관계 주입 이라고 번역이 됩니다.

 

그러면 이제 이전에 만들었던 MemberApp을 한번 수정해볼까요..

public class MemberApp {

    public static void main(String[] args) {
        AppConfig appConfig = new AppConfig();
        MemberService memberService = appConfig.memberService(); // memberService 안에 MemberServiceImpl이 들어가있는 것임
//        MemberService memberService = new MemberServiceImpl();
        Member member = new Member(1L, "MemberA", Grade.VIP);
        
        ...
    }
}

AppConfig라는 클래스가 새로 생성되었기 때문에

이제는 이 녀석을 이용해서 진행해야 합니다.

이렇게 바꾼 것처럼 OrderApp도 바꿔보시기 바랍니다! ㅎ

 

그 외에 테스트 코드에서도 오류가 생긴 것을 알 수 있습니다.

그 부분은 한 번 직접 고쳐 보시면 괜찮을 것 같습니다. ㅎㅎㅎ(아니면 다음 포스트에 올려보겠습니다.)

 

 

이렇게 AppConfig를 이용해서 DIP 성립을 했습니다.

근데 AppConfig의 경우 급하게 한 느낌이 조금 있는 것 같아요.

리팩토링을 해보겠습니다.(이 이후부터는 바쁘시면 스킵해도 될 것 같네요.)

 

일단 코드 상에서

  • new MemoryMemberRepository()가 겹치고 있습니다. 이런 중복은 제거해 주는 것이 좋겠죠.
  • 두루뭉술 할 수도 있지만, 너무 한 군데 모여 있는 느낌이 있습니다. 좀 구분해야 할 필요가 있는 것 같습니다.
    /**
     *  역할을 알 수 있게 한다.
     *  memberRepository() : Member를 저장하는 역할
     */
    private MemberRepository memberRepository() {
        return new MemoryMemberRepository();
    }

    /**
     *  discountPolicy() : 할인에 관련된 부분
     */
    public DiscountPolicy discountPolicy() {
        return new FixDiscountPolicy();
    }

    /**
     *  MemberService가 호출되면?
     *  그러면 MemberServiceImpl이 생성이 됨
     *  그 때 MemoryMemberRepository가 주입이 됨
     */
    public MemberService memberService() {
        return new MemberServiceImpl(memberRepository());
    }

    public OrderService orderService() {
        return new OrderServiceImpl(memberRepository(), discountPolicy());
    }

이렇게 나눠놓으면 좀 더 알아보기 쉽지 않을까요?

그리고 중복도 제거할 수 있습니다!

  1. memberRepository() : 회원을 저장하는 저장소 역할을 하는 것을 알 수 있습니다.
  2. discountPolicy() : 할인과 관련된 부분을 담당하는 것을 알 수 있습니다.
  3. memberService() : 회원을 관리하는 부분입니다. memberRepository가 주입되어야 진행됩니다.
  4. orderService() : 주문 부분입니다. memberRepository와 discountPolicy가 주입되어야 진행됩니다.

이렇게 AppConfig를 리팩토링 해봤습니다.

좀 더 코드 읽기가 편해진 것 같아서 좋네요 ㅎㅎ

 

이번 포스트는 DIP 성립을 위해 코드를 수정해봤습니다.

이렇게 포스트를 마치겠습니다.

(수정)(2020.12.29) 조금 더 추가합니다.

AppConfig를 통해 MemberServiceImpl 이나 OrderServiceImpl의 코드를 고치지 않아도 됩니다.

그럼 저번에 적용해보려던 FixDiscountPolicy -> RateDiscountPolicy를 지금 적용해보겠습니다.

AppConfig의 코드를 다음과 같이 고칩니다.

    public DiscountPolicy discountPolicy() {
//        return new FixDiscountPolicy();
        return new RateDiscountPolicy();
    }

discountPolicy에서 Fix -> Rate으로 바꾸면 됩니다.

그리고 OrderApp에서 한번 확인해 보겠습니다.

20000을 넣으니 할인 금액은 2000이 되었습니다.

할인 방법이 잘 적용되었다고 볼 수 있네요.

만약 의심이 되신다면 AppConfig에서 FixDiscountPolicy를 활성화 한 다음 다시 실행해보시면 됩니다.

 

 

수정을 통해서 DIP 뿐만 아니라 OCP도 지킬 수 있게 되었습니다.

  1. DIP : MemberServiceImpl or OrderServiceImpl에서 인터페이스에만 의존하고 있습니다. 구현체에 의존하지 않습니다.
  2. OCP : Fix -> Rate으로 변경할 때 AppConfig만 수정하면 됩니다. OrderServiceImpl은 수정할 필요가 없습니다.

 

AppConfig를 통해서 구성하는 부분과 사용하는 부분을 나누었기 때문에 DIP, OCP 모두 만족할 수 있게 되었습니다. ㅎ

구성: AppConfig

사용: MemberServiceImpl, OrderServiceImpl 등

 

 

앞으로 이런 식으로 활용을 하면 더 나은 코드를 만들 수 있을 것 같습니다 ㅎ

이번 포스트를 마치겠습니다.

 

 

출처: 인프런 - 스프링 핵심원리(기본편) by 김영한

'JAVA' 카테고리의 다른 글

자바와 스프링 그 사이(마지막)  (0) 2021.01.01
자바와 스프링 그 사이(2)  (0) 2020.12.26
자바와 스프링 그 사이(1)  (0) 2020.12.16
BufferedReader & BufferedWriter  (0) 2020.06.18
Java의 정석을 시작하자(7)(2/11)  (0) 2020.02.14