이렇게 미확정 된 부분은 객체지향 설계를 활용하여 인터페이스를 만들고 구현체를 갈아 끼우는 방식으로 설계해보겠다.
회원 저장소에는 여러가지 구현체들이 들어갈 수 있다.
MemberService 인터페이스를 만들고, 구현체로 MemberServiceImpl을 만든다.
나도 이 글을 적으면서, MemberRepository는 인터페이스를 쓰는건 이해하는데,
왜? memberService를 인터페이스로 구현했지? 싶은 생각이 들었다.
memberService가 변경이 아에 되지 않는다면 인터페이스보다는 class로 구현하는것이 올바르다.
이러한 질문에대한, 댓글을 남긴것을 보면
안녕하세요. ㅇㅇㅇ님
서비스에 인터페이스를 도입하는 것은 장단점이 있습니다.
인터페이스를 만드는 것도 좋지만, 인터페이스를 왜 도입하는지? 라는 질문이 더 중요합니다.
만약 구현체를 전혀 변경할 일이 없다면, 인터페이스를 도입하는 것도 추상화 관점에서 비용이라 생각합니다.
이런 관점에서 실제 실무에서 개발할 때도 서비스에 인터페이스를 도입하는 경우도 있고, 도입하지 않는 경우도 있습니다.
감사합니다.
고로 구현체 Service가 변경할 일이없다면, 인터페이스를 도입하지 않는것이 비용을 아끼는거라고 생각이 들었다.
이번시간은, 객체지향에 대해서 알아보는것이니까 일단 인터페이스로 구현해보았다.
라는 장점이 있습니다.
회원 엔티티
Member에 id,name,grade를 필드로 설정하였습니다.
getter,setter애노테이션을 붙여도되는데, 메소드를 만들었습니다.
회원저장소
앞에서 누차 설명했듯이, interface로 구현하였습니다.
구현체는 여러가지로 바꿔끼울 수 있습니다.
메모리 회원저장소 선택
구현체를 만들었습니다.
Member를 저장하기위해서 Map을 설정하였고, HashMap으로 구현하였습니다.
동시성을 고려하면, concurrentHashmap을 사용해야되지만, 이 내용은 앞에서 스프링입문편 [회원 관리 예제-백엔드 개발] 에서 설명하였습니다.
save,와 findById 메소드를 오버라이딩해서 구현해줬습니다.
회원 서비스 인터페이스
인터페이스로 구현하였습니다.
앞에서 설명한것과 같이 구현체가 아에 변할것이 없다. 고정되어있다. 하면, 굳이 인터페이스로 만들 필요가없고, 비용을 절약하는것이 이득입니다.
회원 서비스 구현체
여기서는 구현체니까 우선 인터페이스를 implents를 해주고,
MemoryMemberRepository를 사용할 것이므로, 객체지향의 다형성을 이용하여서 memberRepository에 new로 객체를 생성해서 넣어줬습니다.
현재까지는 객체지향의 다형성을 잘 사용하였지만, OCP,DIP원칙을 지키지 못한 상태입니다.=>왜? 구현클래스에 의존적이므로, => 구현클래스인 MemboryMemberRepository에 의존하지않고 인터페이스에 의존해야합니다.
그러면 어떻게 해야되는지는 뒤에서 한번에 설명하도록 하겠습니다.
join을 test하는 code를 작성하였다. 항상 test하고자하는 메서드에 @Test 에노테이션을 붙여주고,
MemberService를 사용하기위해서 new MemberService Imple를 생성해서 넣어주었다.
join test에서는 Member를 1L,memberA,VIP로 생성후에, join메서드를 불러 저장을한다.
=>그후에 findMember(1L)로 아이디 1 번의 Memeber를 가져온뒤에 동일한지 검사하였다.
이렇게하면 좋은점이, 주문서비스를 변경하지 않아도, 역할들의 협력관계를 그대로 재사용할 수 있다.
위에서는 메모리에서 조회 아래에서는 DB에서 조회, 위에서는 정액 할인정책, 아래서는 정률할인정책, 구현체들이 전부 변경되었지만, 주문서비스는 변경하지 않아도 된다. 그저, 구현체들만 바꿔끼워주면 된다.
할인 정책 인터페이스
인터페이스로 설계하여서, 나중에 고정할인정책,정률할인정책으로 구현체를 바꿔 끼울 수 있게 설계했습니다. [다형성활용]
정액 할인 정책 구현체
VIP이면 1000을 할인해주게 return 1000원 아니면 0원할인
주문 엔티티
주문을 할때 id와, itemName, itemPrice,discountPrice를 받아서 orderclass를 만들고 주문서비스 구현체에 넘겨주었다.
주문 서비스 인터페이스
사실 주문서비스는 구현체를 바꿔 끼울게 없이 그냥 oder를 만드는 기능이 한가지 있어서 굳이 인터페이스로 안하고, 그냥 class로 만들어도 될꺼같은데, 뭐 다형성을 위해서 일단 예제에서는 이렇게 만들어보자.=>인터페이스도 비용이 드므로, 비용을 아끼기위해서 class로 만드는게 올바른거 같다.
주문 서비스 구현체
우선 member를 찾아야하니까 memberRepository를 만들고,
DiscountPolicy에는 정액 할인정책을 적용할꺼니까 FixDiscountPolicy로 구현체를 넣는다. 나중에 이 Fix가 아니라 Rate비율 discountPolicy로 바꿀것이다.
createOrder메서드는 그냥, member를 찾아서 discountPolicy로 할인 가격을 받아서(vip면 1000 아니면 0) Order를 만들어서 return해준다.
뭐 로직같은것은 너무간단하고, 핵심적인 부분은 단일 책임 원칙을 잘 지켰다는것이다.
만약에 OrderService와 DiscountPolicy를 분리하지 않았다고 생각해보자.
뭔가 어? if 주문이 들어오면, member를 찾아서 vip면 order에 -1000원하고 return order
이거 될꺼같지 않나? 싶을수있다.
그러나 만약에 이제 앞으로 1000원이 아니라 2000원 할인으로 바꿔준다고 치자.
그러면 이 orderService에서 1000원이 아니라 2000으로 바꾸는코드를 전부 찾아서 바꿔줘야한다.
그러나 이 discount와 order를 분리하게되면 위에 있는 코드와 같이, 난 가격할인정책같은건 모르겠고, 그냥 객체 member만 넘겨주면 discount에서 처리해서 반환해줘, 하면
가격할인 정책이 아무리 변경되어도, service는 안고쳐줘도 되는것이다.
long은 원시타입,Long은 참조타입이다.
원시타입은, 실제 메모리에 데이터값을 직접 저장하는거고
참조타입은, 객체의 메모리 주소값을 통해 객체를 참조하는 타입이다.
long에는 그러므로 메모리에 올라가야하므로 null할당이 불가능하다.
만약에 Service가 DB에 들어가고 이런식이라면, null이 들어가야하는 경우도 있을
것 같아서 Long으로 만들어줬다.
그리고 이 Test는 단위Test이다 이게 뭔말이냐하면, SpringBootTest 애노테이션을 달지 않아서, 뭐 컨테이너에서 꺼내쓰거나 DI같은게 없다.
즉, 순수 JAVA코드로만 이루어져 있기 때문에, Test속도가 훨씬빠르다. 그래서 test코드는 단위test로 짜는게 좋다고한다.
단위테스트-Unit Test작성의필요성
실무에서 테스트 코드를 짠다고 하면, 거의 단위테스트를 의미한다.
통합테스트는 실제 여러 컴포넌트들의 상요작용들을 테스트하므로, 모든 컴포넌트들이
구동된상태에서 테스트를 하게 된다.
그러므로 캐시나 DB같은 다른 컴포넌트들과 실제 연결을 하고 이러한 과정에서 많은 비용이 소비된다.
반면에 단위 테스트는 해당 부분만 독립적으로 테스트 하기 때문에 어떤 코드를 리팩토링하여도 빠르게 문제여부를 확인할 수 있다.