1. 관심사의 분리
애플리케이션을 하나의 공연이라고 생각하고, 각 인터페이스를 배역(배우 역할)이라고 생각한다.
공연에서 배역을 정하는 건 배우들이 아니다. 하지만, 이전 코드는 배역(인터페이스)을 맡은 배우(구현체)가 다른 배역(인터페이스)을 하는 배우(구현체)를 직접 초빙하는 것과 같다.
즉 배우가 공연도 해야하는 동시에 배역을 맡은 배우까지 직접 초빙해야 하는 다양한 책임을 가지고 있다.
1) 관심사 분리
- 배우는 본인 역할의 배역을 수행하는 것(공연)에만 집중
- 배우는 어떤 배역의 배우가 선택되더라도 똑같이 공연 가능해야 함
- 공연 구성, 담당 배우 섭외, 역할에 맞는 배우 지정하는 책임을 담당하는 별도의 공연 기획자 필요
공연 기획자를 만들고, 배우와 공연 기획자의 책임을 확실히 분리한다.
2. AppConfig 등장
애플리케이션 전체 동작 방식을 구성(config)하기 위해, 구현 객체를 생성하고 연결하는 책임을 가지는 별도의 설정 클래스를 만든다.
- src/main/java/hello/core/AppConfig.java
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()
);
}
}
1) 애플리케이션 실제 동작에 필요한 구현 객체 생성
# MemberServiceImpl
# MemoryMemberRepository
# OrderServiceImpl
# FixDiscountPolicy
2) 생성한 객체 인스턴스의 참조(레퍼런스)를 생성자를 통해서 주입(연결) (생성자 주입)
# MemberServiceImpl -> MemoryMemberRepository
# OrderServiceImpl -> MemoryMemberRepository, FixDiscountPolicy
지금은 각 클래스에 생성자가 없어서 컴파일 오류가 발생한다. 생성자를 만든다.
3) MemberServiceImpl - 생성자 주입
- src/main/java/hello/core/member/MemberServiceImpl.java
public class MemberServiceImpl implements MemberService {
// private final MemberRepository memberRepository = new MemoryMemberRepository();
private final MemberRepository memberRepository; // final은 생성자를 통해 할당이 되어야 한다.
public MemberServiceImpl(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
...
}
#1 설정 클래스 등장으로 MemberServiceImpl 은 MemoryMemberRepository 의존하지 않고 MemberRepository 인터페이스만 의존
#2 MemberServiceImpl 입장에서 생성자를 통해 어떤 구현체가 들어올지(주입될지) 알 수 없고, 오직 외부(AppConfig)에서 결정된다.
따라서 의존관계에 대한 고민은 외부에 맡기고 실행에만 집중하면 된다.

# DIP 완성 : MemberServiceImpl 은 인터페이스만 의존
# 관심사의 분리 : 객체를 생성하고 연결하는 역할과 실행하는 역할이 명확히 분리

# AppConfig 객체는 MemoryMemberRepository 객체를 생성하고 그 참조값을 MemberServiceImpl 을 생성하면서 생성자에 전달
# 클라이언트 MemberServiceImpl 입장에서 의존관계를 외부에서 주입해주는 것 같다.
이것을 DI(Dependency Injection), 우리말로 의존관계 주입 / 의존성 주입 이라고 한다.
4) OrderServiceImpl - 생성자 주입
- src/main/java/hello/core/order/OrderServiceImpl.java
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;
private final MemberRepository memberRepository;
private final DiscountPolicy discountPolicy;
public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
...
}
#1 설정 클래스 등장으로 OrderServiceImpl 은 FixDiscountPolicy 의존하지 않고 DiscountPolicy 인터페이스만 의존
#2 OrderServiceImpl 입장에서 생성자를 통해 어떤 구현체가 들어올지(주입될지) 알 수 없고, 오직 외부(AppConfig)에서 결정된다.
따라서 의존관계에 대한 고민은 외부에 맡기고 실행에만 집중하면 된다.
#3 OrderServiceImpl 은 AppConfig 설정 클래스에 의해 MemoryMemberRepository, FixDiscountPolicy 객체가 주입된다.
3. AppConfig 실행
1) 사용 클래스 - MemberApp
- src/main/java/hello/core/member/MemberApp.java
public class MemberApp {
public static void main(String[] args) {
AppConfig appConfig = new AppConfig();
// MemberService memberService = new MemberServiceImpl();
MemberService memberService = appConfig.memberService();
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("find Member = " + findMember.getName());
}
}
2) 사용 클래스 - OrderApp
- src/main/java/hello/core/order/OrderApp.java
public class OrderApp {
public static void main(String[] args) {
// MemberService memberService = new MemberServiceImpl();
// OrderService orderService = new OrderServiceImpl();
AppConfig appConfig = new AppConfig();
MemberService memberService = appConfig.memberService();
OrderService orderService = appConfig.orderService();
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); // toString() 호출됨
System.out.println("order.calculatePrice = " + order.calculatePrice());
}
}
3) 테스트 코드 오류 수정
- src/test/java/hello/core/member/MemberServiceTest.java
public class MemberServiceTest {
// MemberService memberService = new MemberServiceImpl();
MemberService memberService;
@BeforeEach
public void beforeEach() {
AppConfig appConfig = new AppConfig();
memberService = appConfig.memberService();
}
@Test
void join() {
...
}
}
- src/test/java/hello/core/order/OrderServiceTest.java
public class OrderServiceTest {
// MemberService memberService = new MemberServiceImpl();
// OrderService orderService = new OrderServiceImpl();
MemberService memberService;
OrderService orderService;
@BeforeEach
public void beforeEach() {
AppConfig appConfig = new AppConfig();
memberService = appConfig.memberService();
orderService = appConfig.orderService();
}
@Test
void createOrder() {
...
}
}
# @BeforeEach : 테스트 코드에서 각 테스트 실행 전에 호출됨
4. 정리
1) AppConfig 는 공연 기획자 역할
배역에 맞는 담당 배우를 선택, 즉 구현체를 선택하여 애플리케이션이 어떻게 동작해야 할지 전체 구성을 책임.
따라서 각 배우들은 담당 역할에만 집중, 구현체는 실행에만 집중
2) AppConfig 를 통해서 관심사를 확실하게 분리
참고링크 : https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8?cid=325969
스프링 핵심 원리 - 기본편| 김영한 - 인프런 강의
현재 평점 5.0점 수강생 49,522명인 강의를 만나보세요. 스프링 입문자가 예제를 만들어가면서 스프링의 핵심 원리를 이해하고, 스프링 기본기를 확실히 다질 수 있습니다. 스프링 기본 기능, 스프
www.inflearn.com
'강의 실습 > 스프링 핵심 원리 - 기본편' 카테고리의 다른 글
| 새로운 구조와 할인 정책 적용 (0) | 2026.04.03 |
|---|---|
| AppConfig 리팩터링 (0) | 2026.04.03 |
| 새로운 할인 정책 적용과 문제점 (0) | 2026.04.01 |
| 새로운 할인 정책 개발 (0) | 2026.03.31 |
| 주문과 할인 도메인 실행과 테스트 (0) | 2026.03.30 |
댓글