일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- JPA
- Stateless
- VUE
- Security
- 캐시
- Vue.js
- javascript
- cache
- 라이프 사이클
- thymeleaf
- vue-cli
- di
- dependency injection
- js
- 의존성 주입
- 프로토타입
- Singleton
- DB
- Excel
- Setter
- Spring
- BEAN
- vuex
- Kotlin
- Java
- Repository
- HTTP
- 로그인
- HTTP 메서드
- 싱글톤
- Today
- Total
jhhan의 블로그
자바와 스프링 그 사이(3) 본문
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());
}
이렇게 나눠놓으면 좀 더 알아보기 쉽지 않을까요?
그리고 중복도 제거할 수 있습니다!
- memberRepository() : 회원을 저장하는 저장소 역할을 하는 것을 알 수 있습니다.
- discountPolicy() : 할인과 관련된 부분을 담당하는 것을 알 수 있습니다.
- memberService() : 회원을 관리하는 부분입니다. memberRepository가 주입되어야 진행됩니다.
- 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도 지킬 수 있게 되었습니다.
- DIP : MemberServiceImpl or OrderServiceImpl에서 인터페이스에만 의존하고 있습니다. 구현체에 의존하지 않습니다.
- 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 |