스프링 핵심원리 기본편2 - 생성자 주입
안녕하세요
오늘도 수업 내용을 정리해 보겠습니다.
관심사의 분리에 관한 쉬운 이해를 위해 김영한 님은 인터페이스를 배역 구현체를 배우로 생각하라고 하셨습니다.
배우가 배역의 배우를 직접 초빙하는 것은 배역을 연기하는 일과 배역을 배정하는 일 즉 다양한 책임을 가지고 있는 거라 좋은 설계가 아니고 배우는 본인의 역할인 배역을 수행하는 것에만 집중해야 한다. 하셨습니다.
1
2
3
4
5
|
public class OrderServiceImpl implements OrderService{
private final MemberRepository memberRepository = new MemoryMemberRepository();
// private final DiscountPolicy discountPolicy = new FixDiscountPolicy();//고정
private final DiscountPolicy discountPolicy = new RateDiscountPolicy();//정율
|
cs |
소스에서 본다면 위에 OrderServiceImpl이 어떤 할인 정책(고정/정률)을 적용할 것인지 직접 선택하는 것은 코딩 방법이다.
해당 문제를 해결하기 위해서는 core 아래 AppConfig 클래스 파일은 생성해 주고
아래 소스와 같이
MemberServiceImpl에서 MemberRepository를 생성자를 만들고 = new MemoryMemberRepository();를 주석처리 해줍니다.
1
2
3
4
5
6
7
8
|
public class MemberServiceImpl implements MemberService{ //6번
private final MemberRepository memberRepository;// = new MemoryMemberRepository();
public MemberServiceImpl(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
|
cs |
생성자를 통해서 MemberRepository 들어갈 값을 정해줍니다.
아래 소스는 AppConfig에서 멤버 서비스를 불러 쓰기 위한 코드입니다.
1
2
3
4
5
6
7
8
9
10
11
12
|
package hello.core;
import hello.core.member.*;
public class AppConfig {
public MemberService memberService() {
return new MemberServiceImpl(new MemoryMemberRepository());
}
}
|
cs |
이와 같이 AppConfig에서 만들어진 객체가 MemberServiceImpl의 생성자로 들어가게 된것을 생성자 주입이라고 합니다.
마찬가지로 AppConfig에 오더서비스를 만들고
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
package hello.core;
import hello.core.member.*;
import hello.core.order.*;
import hello.core.discount.*;
public class AppConfig {
public MemberService memberService() {
return new MemberServiceImpl(new MemoryMemberRepository());
}
public OrderService oderService() {
return new OrderServiceImpl(new MemoryMemberRepository(),new FixDiscountPolicy());// 요기에서 이제 할인 정책을 수정해 줄수 있다.
}
}
//public OrderService orderService() {
// return
//}
|
cs |
아래 소스와 같이 OrderServiceImpl 생성자를 만들어주어 생성자 주입을 해줍니다.
1
2
3
4
5
6
7
8
9
10
11
12
|
public class OrderServiceImpl implements OrderService{
private final MemberRepository memberRepository; //= new MemoryMemberRepository();
// private final DiscountPolicy discountPolicy = new FixDiscountPolicy();
// private final DiscountPolicy discountPolicy = new RateDiscountPolicy();
// private DiscountPolicy discountPolicy; //final은 무조껀 값이 할당되어야함
private final DiscountPolicy discountPolicy;
public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
|
cs |
이렇게 생성자 주입을 통해 소스를 작성하게 되면
DIP를 철저하게 지킬 수 있습니다. OrderServiceImpl은 인터페이스에만 의존 구체적인 클래스에 대해서는 전혀 모르게 됩니다.
즉 AppConfig는 애플리 케이션의 실제 동작에 필요한 구현 객체를 생성, 생성한 객체 인스턴스의 참조(레퍼런스)를 "생성자를 통해서 주입(연결) 해줍니다."
설계 변경으로 MemberServiceImpl은 MemoryMemberRepository를 의존하지 않습니다.
단지 MemberRepository 인터페이스에만 의존합니다.
어떤 구현 객체가 들어올지는 외부(AppConfig)에서 결정
이제부터 impl은 의존관계에 대한 고민은 외부에 맡기고 실행에만 집중
OrderServiceImpl도 MemberServiceImpl의 원리와 동일합니다.
실제 실행을 위해서는 AppConfig를 생성해 주고 MemberServiceImpl()를 직접 생성해 주지 않고 아래의 코드처럼 appConfig을 사용해 줍니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
package hello.core;
import hello.core.member.Grade;
import hello.core.member.Member;
import hello.core.member.MemberService;
import hello.core.member.MemberServiceImpl;
public class MemberApp {
public static void main(String[] args) {
AppConfig appConfig = new AppConfig();
MemberService memberService = appConfig.memberService();
// MemberService memberService = new MemberServiceImpl();
Member member = new Member(1L,"memberA",Grade.VIP);
memberService.join(member);
Member findMember = memberService.findMember(1L);
System.out.println("new member = " + member.getName());
System.out.println("findMember = " + findMember.getName());
}
}
|
cs |
order의 실제 실행을 위해서 수정해 준 코드는 아래와 같습니다.
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
|
package hello.core;
import hello.core.member.*;
import hello.core.order.*;
public class OrderApp {
public static void main(String[] args) {
AppConfig appConfig = new AppConfig();
MemberService memberService = appConfig.memberService();
OrderService orderService = appConfig.oderService();
// MemberService memberService = new MemberServiceImpl();
// OrderService orderService = new OrderServiceImpl();
Long memberId = 1L;
Member member = new Member(memberId,"MemberA",Grade.VIP);
memberService.join(member);
Order order = orderService.createOrder(memberId, "itemA", 10000);
System.out.println("order = " + order);
System.out.println("ordercalculatePrice = " + order.calculatePrice());
}
}
|
cs |
테스트를 위해 테스트 코드에서도 모두 생성자 주입을 위해 아래처럼 AppConfig로 변경을 해주어야 합니다.
1
2
3
4
5
6
7
8
9
|
public class MemberServiceTest {
MemberService memberService;// = new MemberServiceImpl();
@BeforeEach // 테스트 실행전에 무조껀 실행된다.
public void deforeEach() {
AppConfig appConfig = new AppConfig();
memberService = appConfig.memberService();
}
|
cs |
위의 @ BeforeEach는 테스트 실행 전 무조건 실행이 됩니다.
orderService의 테스트 코드는 아래와 같습니다.
1
2
3
4
5
6
7
8
9
10
11
|
public class OrderServiceTest {
MemberService memberService;// = new MemberServiceImpl();
OrderService orderService;// = new OrderServiceImpl();
@BeforeEach
public void deforeEach() {
AppConfig appConfig = new AppConfig();
memberService = appConfig.memberService();
orderService = appConfig.oderService();
}
|
cs |
오늘은 자바로 웹을 개발하기 위해 가장 중요하다고 할 수 있는 역할과 구현을 따로 해주기 위해 생성자 주입을 배워서 조금 더 이해를 많이 할 수 있는 기회가 된 거 같습니다.