Spring
스프링 핵심 원리[기본편] - 예제만들기 , 객체 지향 원리 적용
study ticket
2022. 12. 27. 11:13
예제 만들기
주문과 할인 도메인 설계
주문 도메인 협력, 역할, 책임
주문 도메인 전체
주문 도메인 클래스 다이어그램
주문과 할인 도메인 개발
주문서비스 구현체
- 주문 구현체를 작성시 다음과 같이 인스턴스를 생성해주면 OCP, DIP 같은 객체지향 설계원칙을 준수하지 못한다.
- DIP : 주문서비스 클라이언트(OrderServiceImpl)은 추상(인터페이스)뿐만 아닌 구체 클래스(FixDiscountPolicy)를 의존하고 있다.
- OCP : 만약 할인 정책을 변경하거나 저장소를 변경할시 클라이언트(구현체)에 영향을 준다.(일일이 변경해줘야한다.)
public class OrderServiceImpl implements OrderService {
private final MemberRepository memberRepository = new
MemoryMemberRepository();
private final DiscountPolicy discountPolicy = new FixDiscountPolicy();
}
의존관계 분석
이상적인 의존관계
- 구현체가 단순히 DiscountPolicy 인터페이스만을 의존한다.
실제 의존관계
- 클라이언트(OrderServiceImpl)이 인터페이스(DiscountPolicy)뿐만 아닌 구체 클래스(FixDiscountPolicy)를 함께 의존하고 있다. -> 기획이 변경될때 구체 클래스까지 변경해줘야하므로 OCP위반
변경 예시
- 다음과 같이 인터페이스에만 의존하도록 코드를 변경해야한다.
- -> 누군가 클라이언트인 OrderServiceImpl에 DiscountPolicy의 구현객체를 대신 생성하고 주입해줘야한다.
- -> Appconfig등장!!
public class OrderServiceImpl implements OrderService {
//private final DiscountPolicy discountPolicy = new RateDiscountPolicy();
private DiscountPolicy discountPolicy;
}
AppConfig 등장
- 애플리케이션의 전체 동작방식을 구성(config)하기 위해, 구현객체를 생성하고, 연결하는 책임을 가지는 별도의 설정 클래스를 만들어줍니다.
- 실제 동작에 필요한 구현객체를 생성
- 생성자를 통해 주입해준다.
- OrderServiceImpl -> MemoryMemberRepository , FixDiscountPolicy
AppConfig
- 설계 변경으로 OrderServiceImpl은 MemoryMemberRepository와 FixDiscountPolicy를 의존하지 않는다.
- 단지 인터페이스만 의존하고, 어떤 구현객체가 주입될지는 알수 없고 알 필요도 없다 단지 실행에만 집중하면 된다. 즉 비즈니스 로직상의 인터페이스의 구현객체가 다른 객체로 변경되어도 AppConfig만 수정하면 사용영역의 어떠한 코드변경없이 확장할수 있다.(OCR만족)
public class AppConfig {
public MemberService memberService() {
return new MemberServiceImpl(new MemoryMemberRepository());
}
public OrderService orderService() {
return new OrderServiceImpl(
new MemoryMemberRepository(),
new FixDiscountPolicy());
}
}
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 리팩터링
리팩토링하기 전
- 중복이 존재(MemoryMemberRepository)하고, 역할에 따른 구현(new ...)이 명확히 보이지 않음
public class AppConfig {
public MemberService memberService() {
return new MemberServiceImpl(new MemoryMemberRepository());
}
public OrderService orderService() {
return new OrderServiceImpl(
new MemoryMemberRepository(),
new FixDiscountPolicy());
}
}
리팩토링 후
- 중복을 제거(memberRepository()로 대체)하고, memberRepository로 묶음으로써 MemoryMemberRepository를 다른 구현체로 변경할때 한부분만 변경하면 된다.
- 역할과 구현클래스가 명확히 보인다.
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();
}
}
새로운 구조와 할인 정책 적용
- 구현체를 변경해도 구성영역(AppConfig)만 영향을 받고 사용영역은 전혀 영향받지 않는다.
IoC, DI, 그리고 컨테이너
제어의 역전 IoC
- 기존 프로그램은 클라이언트 구현객체가 스스로 필요한 서버 구현 객체를 생성하고, 연결하고, 실행하였다. 쉽게말하자면 필요한 클래스가 있으면 new를 통해 인스턴스를 생성해주고, 사용하듯 프로그램의 제어흐름을 스스로 조종했다.
- 반면에 AppConfig가 등장한 이후에 구현객체(클라이언트)는 자신의 로직을 실행하는 역할만 담당하고, 그외의 제어흐름은 AppConfig가 가져간다. 예로들어 OrderServiceImpl에 필요한 인터페이스들을 호출하지만 어떤 구현체가 들어온지는 모르지만 자신의 로직을 이어나간다.
- 이렇듯 프로그램의 제어흐름을 클라이언트가 직접 제어하는것이 아닌 외부(AppConfig)에서 관리하는것을 제어의 역전(IoC)라고 한다.
의존관계주입 DI
- OrdereServiceImpl은 DiscountPolicy 인터페이스에 의존하지만, 실제 어떤 구현객체가 들어올지는 모른다.
- 의존관계는 정적인 의존관계와 실행 시점에 결정되는 동적인 객체의존관계를 분리해서 생각해야한다.
정적인 클래스 의존관계
- impor코드만 보고 의존관계를 쉽게 판단할수 있다.
- 하지만 실제로 어떤 구현객체가 사용되는지 구체적으로 알기는 힘들다.
동적인 객체 인스턴스 의존 관계
- 애플리케이션 실행 시점에 실제 생성된 객체 인스턴스의 참조가 연결된 의존 관계다.
DI정리
- 애플리케이션 실행시점에 외부에서 실제 구현객체를 생성하고 클라이언트에 전달하여 서로 의존관계가 연결되는 것을 의존관계 주입이라 한다.
- 객체 인스턴스를 생성하고 return을 통해 참조값을 전달하여 연결한다.
- 의존관계주입을 사용하면 클라이언트 코드를 변경하지 않고, 클라이언트가 호출하는 대상의 타입 인스턴스를 변경할수 있다(AppConfig를 통해).
- 의존관계 주입을 사용하면 정적인 클래스 의존관계를 변경하지 않고, 동적인 객체 인스턴스 의존관계를 쉽게 변경할수 있다.
DI 컨테이너
- AppConfig 처럼 객체를 생성하고 관리하면서 의존관계를 연결해 주는 것
- 스프링으로 전환하기
- 지금까지 순수한 자바코드로 DI를 구현하였으니 스프링을 사용해보자
AppConfig 스프링 기반으로 변경
- @Configuration : AppConfig에 설정을 구성해준다는 뜻
- @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();
}
}
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)
- 필요한 객체를 이전에는 AppConfig를 통해 직접 조회했지만, 이제는 컨테이너를 통해 필요한 스프링빈을 찾아야한다.
- applicationContext.getBean()을 통해 찾을수 있다.
참고강의
728x90