728x90
새로운 할일 정책 적용
할인 정책을 FixDiscountPolicy에서 RateDiscountPolicy로 변경하려고 한다.
public class OrderServiceImpl implements OrderService {
// private final DiscountPolicy discountPolicy = new FixDiscountPolicy();
private final DiscountPolicy discountPolicy = new RateDiscountPolicy();
}
문제점
위 코드처럼 할인 정책을 변경하려면 클라이언트인 OrderServiceImpl코드를 고쳐야 한다.
SOLID의 원칙에 따르면 DIP와 OCP를 위반하고 있다.
SRP: 단일 책임 원칙(single responsibility principle)
OCP: 개방-폐쇄 원칙 (Open/closed principle)
LSP: 리스코프 치환 원칙 (Liskov substitution principle)
ISP: 인터페이스 분리 원칙 (Interface segregation principle)
DIP: 의존관계 역전 원칙 (Dependency inversion principle)
DIP: 클라이언트 코드가 구현 클래스를 바라보지 말고 인터페이스만 바라봐라
- OrderServiceImpl이 할인 정책 인터페이스 뿐만 아니라 할인 정책의 구현체에도 의존하고 있음
OCP: 변경하지 않고 확장할 수 있다.
- 기능을 확장해서 변경하면, 클라이언트 코드에 영향을 준다.
해결 방법
인터페이스에만 의존하도록 변경
public class OrderServiceImpl implements OrderService {
//private final DiscountPolicy discountPolicy = new RateDiscountPolicy();
private DiscountPolicy discountPolicy;
}
클라이언트인 OrderServiceImpl에 DiscountPolicy의 구현 객체를 대신 생성하고 주입해주어야함.
AppConfing라는 리포지토리를 따로 만들어서 생성한 객체 인스턴스의 참조를 생성자를 통해서 주입 해주면 된다.
AppConfig
public class AppConfig {
public MemberService memberService() {
return new MemberServiceImpl(new MemoryMemberRepository());
}
public OrderService orderService() {
return new OrderServiceImpl(
new MemoryMemberRepository(),
new FixDiscountPolicy());
}
}
OrderServiceImpl
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;
}
이렇게 AppConfig 리포지토리에서 생성자를 통한 의존관계주입으로 DIP와 관심사 분리가 해결 됐다.
관심사 분리: 객체를 생성하고 연결하는 역할과 실행하는 역할이 명확이 분리
이런 의존 관계 주입은 서비스를 실행하기 전에 주입 되어야하므로 @BeforEach를 사용해서 테스트 실행하기 전에 의존관계 주입을 하도록 한다.
@BeforeEach
public void beforeEach() {
AppConfig appConfig = new AppConfig();
memberService = appConfig.memberService();
orderService = appConfig.orderService();
}
여기까지 좋은 객체 지향 설계의 5가지 원칙중 3가지 적용
SRP 단일 책임 원칙
- 한 클래스는 하나의 책임만 가져야 한다.
- SRP 단일 책임 원칙을 따르면서 관심사를 분리함
- 구현 객체를 생성하고 연결하는 책임은 AppConfig가 담당, 클라이언트 객체는 실행하는 책임만 담당
DIP 의존관계 역전 원칙
- 프로그래머는 “추상화에 의존해야지, 구체화에 의존하면 안된다.” 의존성 주입은 이 원칙을 따르는 방법 중 하나다
OCP: 소프트웨어 요소는 확장에는 열려 있으나 변경에는 닫혀 있어야 한다
- 애플리케이션을 사용 영역과 구성 영역으로 나눔
- AppConfig가 의존관계를 FixDiscountPolicy RateDiscountPolicy 로 변경해서 클라이언트 코드에 주입하므로 클라이언트 코드는 변경하지 않아도 됨
이제 위에서 자바코드로 구현한 것을 스프링으로 전환해보자
1. 설정 정보를 작성한 AppConfig 스프링 기반으로 변경
- AppConfig에 설정을 구성한다는 뜻의 @Configuration 을 붙여준다.
- 각 메서드에 @Bean 을 붙여준다. 이렇게 하면 스프링 컨테이너에 스프링 빈으로 등록한다.
@Configuration
public class AppConfig {
@Bean
public MemberService memberService() {
return new MemberServiceImpl(memberRepository());
}
@Bean
public OrderService orderService() {
return new OrderServiceImpl(memberRepository(), discountPolicy());
}
@Bean
public MemberRepository memberRepository() {
return new MemoryMemberRepository();
}
@Bean
public DiscountPolicy discountPolicy() {
return new RateDiscountPolicy();
}
}
2. MemberApp에 스프링 컨테이너 적용
public class MemberApp {
public static void main(String[] args) {
// AppConfig appConfig = new AppConfig();
// MemberService memberService = appConfig.memberService();
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class); //ApplicationContext 를 스프링 컨테이너
MemberService memberService = applicationContext.getBean("memberService", MemberService.class);
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());
}
}
3. OrderApp에 스프링 컨테이너 적용
public class OrderApp {
public static void main(String[] args) {
// AppConfig appConfig = new AppConfig();
// MemberService memberService = appConfig.memberService();
// OrderService orderService = appConfig.orderService();
ApplicationContext applicationContext = new
AnnotationConfigApplicationContext(AppConfig.class);
MemberService memberService = applicationContext.getBean("memberService", MemberService.class);
OrderService orderService = applicationContext.getBean("orderService", OrderService.class);
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);
}
}
스프링 컨테이너
- ApplicationContext 를 스프링 컨테이너라 한다
- 기존에는 개발자가 AppConfig 를 사용해서 직접 객체를 생성하고 DI를 했지만, 이제부터는 스프링 컨테이너를 통해서 사용한다.
- 스프링 컨테이너는 @Configuration 이 붙은 AppConfig 를 설정(구성) 정보로 사용한다. 여기서 @Bean이라 적힌 메서드를 모두 호출해서 반환된 객체를 스프링 컨테이너에 등록한다. 이렇게 스프링 컨테이너에 등록된 객체를 스프링 빈이라 한다.
- 스프링 빈은 @Bean 이 붙은 메서드의 명을 스프링 빈의 이름으로 사용한다. ( memberService , orderService)
- 이전에는 개발자가 필요한 객체를 AppConfig 를 사용해서 직접 조회했지만, 스프링 컨테이너를 통해서 필요한 스프링 빈(객체)를 찾아야 한다. 스프링 빈은applicationContext.getBean() 메서드를 사용해서 찾을 수 있다.
728x90
'Spring > Spring 핵심 원리' 카테고리의 다른 글
의존관계 주입 - 조회 빈이 2개 이상 (0) | 2023.09.17 |
---|---|
의존관계 주입 - 롬복과 최신 트랜드 (0) | 2023.09.17 |
의존관계 자동 주입 (0) | 2023.09.17 |
싱글톤 컨테이너 (0) | 2023.09.16 |
스프링 컨테이너와 스프링 빈 (0) | 2023.09.16 |