이전 포스팅에서는 할인 정책을 교체하기 어려운 구조로 인해 OCP와 DIP를 위반하게 되는 문제를 살펴봤다.
이번에는 그 문제를 해결하기 위해 등장한 AppConfig라는 설정 클래스를 중심으로 관심사의 분리와 의존 관계 주입 구조를 적용해보자.
객체 생성을 외부로 분리하자
서비스 클래스는 자신의 역할(로직 구현)에만 집중하고,
필요한 객체의 생성과 연결은 AppConfig가 담당하도록 한다.
public class AppConfig {
public MemberService memberService() {
return new MemberServiceImpl(new MemoryMemberRepository());
}
public OrderService orderService() {
return new OrderServiceImpl(
new MemoryMemberRepository(),
new FixDiscountPolicy()
);
}
}
이처럼 객체를 생성하고 연결해주는 설정 클래스를 통해 DIP를 만족시킬 수 있고,
새로운 구현체로 교체할 때 AppConfig 코드만 바꾸면 되므로 OCP도 지킬 수 있다.
생성자 주입 방식
AppConfig는 객체를 생성하면서 생성자를 통해 의존 객체를 전달한다. 이를 생성자 주입 방식이라고 한다.
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;
}
}
이 방식의 장점은 다음과 같다:
- 객체가 생성될 때 의존관계를 명확하게 설정할 수 있음
- 불변성 보장이 가능함 (final로 선언 가능)
- 테스트 코드 작성이 용이함
AppConfig 리팩터링
AppConfig가 커지면서 중복이 발생할 수 있다. 이를 해결하고 구조를 더 명확하게 하기 위해 다음과 같이 리팩터링할 수 있다.
public class AppConfig {
public MemberService memberService() {
return new MemberServiceImpl(memberRepository());
}
public OrderService orderService() {
return new OrderServiceImpl(
memberRepository(),
discountPolicy()
);
}
public MemberRepository memberRepository() {
return new MemoryMemberRepository();
}
public DiscountPolicy discountPolicy() {
return new FixDiscountPolicy(); // 나중에 여기만 바꾸면 됨
}
}
리팩터링 효과
- 역할이 명확하게 드러난다 (discountPolicy()를 보면 어떤 구현을 쓰는지 바로 알 수 있음)
- 중복 제거 (new MemoryMemberRepository()가 여러 번 등장하는 문제 해소)
- 새로운 구현체 교체 시 한 곳만 수정하면 됨 → OCP 만족
전체 구조 정리
AppConfig를 기준으로 구성 영역과 사용 영역을 명확히 나눈 구조는 다음과 같다:
- 구성 영역 (AppConfig): 객체 생성, 의존관계 연결
- 사용 영역 (ServiceImpl 등): 자신의 로직만 구현, 의존 객체가 무엇인지 모름
이로써 우리는 객체 지향 설계 원칙 중 다음을 만족시켰다:
- SRP: 설정 책임과 서비스 책임을 분리
- DIP: 인터페이스에 의존, 구현체에는 의존하지 않음
- OCP: 구현체가 바뀌어도 클라이언트 코드는 수정하지 않아도 됨
다음 편에서는 이 구조에 새로운 할인 정책(RateDiscountPolicy)을 적용해보고,
스프링의 DI 컨테이너로 전환하는 과정을 살펴본다.
'Spring' 카테고리의 다른 글
[Spring 완전 정복 시리즈] 10편 - 스프링 컨테이너의 시작과 빈 등록 (1) | 2025.07.27 |
---|---|
[Spring 완전 정복 시리즈] 9편 - 스프링으로 전환하고 의존관계 주입 자동화하기 (1) | 2025.07.26 |
[Spring 완전 정복 시리즈] 7편 - DIP/OCP 위반 구조의 문제점 (1) | 2025.07.26 |
[Spring 완전 정복 시리즈] 6편 - 예제 만들기: 역할과 구현 분리의 시작 (1) | 2025.07.26 |
[Spring 완전 정복 시리즈] 5편 - 객체 지향 설계와 스프링: DI가 왜 중요한가? (0) | 2025.07.23 |