이 글은 강의 : 김영한님의 - "스프링 핵심원리 - 기본편"을 듣고 정리한 내용입니다. 😁😁
다시 처음에 생각했던 부분을 생각해보자.
공연(애플리케이션)에서 각각 인터페이스는 배역이라고 할 수 있다. 그렇다면 이 배역을 연기하는 배우는 구현 객체일 것이다.
그런데 실제 배역을 맞는 배우를 선택하는 것은 누가 하는가?
현재까지 작성한 코드는 로미오와 줄리엣이라는 공연(애플리케이션)에서, 로미오 역할(인터페이스)을 하는 배우인 레오나르도 디카프리오(구현객체) 가 줄리엣 역할(인터페이스)을 하는 여자 주인공(구현객체)를 직접 캐스팅하는 것과 마찬가지인 내용이다. 디카프리오는 직접 공연도 하고, 여자 주인공도 캐스팅하는 다양한 역할을 하고 있는 셈이다.
- 배우는 자신이 맡은 배역을 수행하는 역할만 수행하는 데 집중하는 것이 맞다.
- 디카프리오는 상대 여자 주인공이 어떤 사람이 되더라도 동일하게 공연을 할 수 있어야 한다.
- 공연을 구성하고, 담당 배우를 섭외하고, 역할에 맞는 배우를 지정하는 것 책임을 갖는 공연 기획자가 필요하다
- 공연 기획자를 만들어서, 배우와 공연 기획자의 책임을 분리해서 작성해보도록 하자.
공연기획자의 역할을 하는 것이 바로 AppConfig이다. 애플리케이션의 전체 동작 방식을 구성하기 위해서, 구현 객체(구체화)를 생성하고, 연결하는 책임을 갖는 별도의 설정 클래스이다.
package hello.core;
import hello.core.discount.FixDiscountPolicy;
import hello.core.member.MemberService;
import hello.core.member.MemberServiceImpl;
import hello.core.member.MemoryMemberRepository;
import hello.core.order.OrderService;
import hello.core.order.OrderServiceImpl;
public class AppConfig {
// 생성자 주입
public MemberService memberService() {
return new MemberServiceImpl(new MemoryMemberRepository());
}
// 생성자 주입
public OrderService orderService() {
return new OrderServiceImpl(new MemoryMemberRepository(), new FixDiscountPolicy());
}
}
AppConfig는 애플리케이션의 동작에 필요한 구현객체를 생성한다.
그리고 생성한 객체의 인스턴스 참조(레퍼런스)을 생성자를 통해서 주입(연결한다).
package hello.core.member;
public class MemberServiceImpl implements MemberService{
// MemberServiceImpl 은 MemberRepository 인터페이스에만 의존
private final MemberRepository memberRepository;
// 의존성 주입(DI)
public MemberServiceImpl(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
@Override
public void join(Member member) {
memberRepository.save(member);
}
@Override
public Member findMember(Long memberId) {
return memberRepository.findById(memberId);
}
}
기존에는 MemberServiceImpl이 MemoryMemberRepository를 직접 생성했지만 설계 변경 후,
MemberServiceImpl → MemoryMeberRepository 구현 객체 의존X
MemberServiceImpl → MemberRepository 인터페이스에만 의존으로 변경!
MemberServiceImpl은 생성자를 통해서 어떤 구현객체가 들어오는지는 알 수 없다. 이제 구현객체는 외부 ( AppConfig ) 를 통해서만 결정된다.
MemberServiceImpl 은 이제부터 의존관계에 대한 고민은 외부에 맡기고 실행에만 집중하면 된다!
클래스 다이어그램을 보면 더 이상 구현 객체를 의존하지 않게 된다. 객체의 생성과 연결은 외부인 AppConfig를 통해서 이루어지게 된 모습을 볼 수 있다.
이렇게 구현 객체를 의존하지 않고, 인터페이스만 의존하게 되어 DIP가 완성된 모습이다.
즉 여기서 관심사의 분리 : 객체를 생성, 연결하는 역할, 실행하는 역할이 분리됨을 볼 수 있다.
AppConfig는 MemoryMemberRepository를 생성해서 참조값을 MemberServiceImpl를 생성하면서 생성자에 전달한다.
이렇게 주문 서비스도 회원 서비스와 동일하게 생성자를 통해서 의존성을 주입받도록 한다. 더 이상 할인 정책이나 저장에 대한 부분을 직접 결정하지 않아도 된다.
package hello.core.order;
import hello.core.discount.DiscountPolicy;
import hello.core.member.Member;
import hello.core.member.MemberRepository;
import hello.core.member.MemoryMemberRepository;
public class OrderServiceImpl implements OrderService{
// 인터페이스에만 의존하도록 변경(DIP 만족)
private final MemberRepository memberRepository;
private final DiscountPolicy discountPolicy;
// 의존성 주입(DI)
public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
@Override
public Order createOrder(Long memberId, String itemName, int itemPrice) {
Member member = memberRepository.findById(memberId); // 해당 id를 가진 회원 조회
int discountPrice = discountPolicy.discount(member, itemPrice); // 회원의 등급에 따른 할인 금액
return new Order(memberId, itemName, itemPrice, discountPrice); // 최종 생성된 주문 반환
}
}
기존에는 OrderServiceImpl이 MemoryMemberRepository, FixDiscountPolicy 구현 객체를 직접 생성했지만 설계 변경 후에,
OrderServiceImpl → MemoryMeberRepository, FixDiscountPolicy 구현 객체 의존X
OrderServiceImpl → MemberRepository, DiscountPolicy 인터페이스에만 의존으로 바뀜.
먼저 회원서비스
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());
}
}
MemberApp 클래스에서 MemberServiceImpl을 직접 생성하지 않고 생성한 AppConfig를 이용하여 MemberServiceImpl을 생성하고 MemberService에 연결해준다.
주문 서비스
package hello.core;
import hello.core.member.Grade;
import hello.core.member.Member;
import hello.core.member.MemberService;
import hello.core.member.MemberServiceImpl;
import hello.core.order.Order;
import hello.core.order.OrderService;
import hello.core.order.OrderServiceImpl;
public class OrderApp {
public static void main(String[] args) {
AppConfig appConfig = new AppConfig();
MemberService memberService = appConfig.memberService();
OrderService orderService = appConfig.orderService();
//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); // order.toString() 출력됨
//System.out.println("order.calculatePrice = " + order.calculatePrice()); // 할인된 최종 가격
}
}
OrderApp클래스에서 MemberServiceImpl을 직접 생성하지 않고 생성한 AppConfig을 이용하여 MemberServiceImpl을 생성하고 MemberService에 연결해준다
OrderServiceImpl을 직접 생성하지 않고 생성한 AppConfig을 이용하여 OrderServiceImpl을 생성하고 OrderService에 연결해준다.
(둘다 결국 구현객체를 만든걸 추상화(인터페이스)로 받아서 사용하는거야 ! DIP만족하는거지.)
정리해보면,
_AppConfig을 통해서 관심사를 확실하게 분리했다. 각자 역할은 자신이 맡은 실행만 하면 되고, 전체 구성은 외부의 기획자인 AppConfig가 책임을 지게 되는 것이다._