스프링 기본 section4

Sean·2025년 4월 11일

새로운 할인 정책

  • RateDiscountPolicy로 교체한다면?
public class OrderServiceImpl implements OrderService {
// private final DiscountPolicy discountPolicy = new FixDiscountPolicy();
private final DiscountPolicy discountPolicy = new RateDiscountPolicy();
}

문제 : 정책 갈아끼울 시, OrderServiceImpl 코드 수정해야함 -> OCP, DIP 안 지켜짐.

  1. OCP -> 변경에 닫혀있어야 하나( 변경하더라도 수정 없이 확장 ), 변경시 코드 수정해야함.
  2. DIP -> ServiceImpl 이 구현체(RateDiscountPolicy,FixDiscountPolicy ) 까지 의존하고있음.

어떻게 문제 해결?

추상에만 의존하도록 수정하면 되겠네.

public class OrderServiceImpl implements OrderService {
//private final DiscountPolicy discountPolicy = new RateDiscountPolicy();
private DiscountPolicy discountPolicy;
}

Q. 구현 객체 빼긴 했는데, 이대로는 실행 안됨. 변수에 객체가 들어가긴 해야함. 어떻게 하지?
-> 책임 분리해보자. 역할을 부여해주는 객체도 따로 만들자 -> AppConfig

AppConfig 등장

  • 구현 객체 생성 + 연결 책임
  • 사용 영역구성 영역(Configuration) 으로 분리.
Appconfig
public class AppConfig {
	public MemberService memberService() {
		return new MemberServiceImpl(new MemoryMemberRepository());
		}

	public OrderService orderService() {
		return new OrderServiceImpl(
			new MemoryMemberRepository(),
			new FixDiscountPolicy());
		}
}
MemberServiceImpl
public class MemberServiceImpl implements MemberService {
	private final MemberRepository memberRepository;

	public MemberServiceImpl(MemberRepository memberRepository) {
		this.memberRepository = memberRepository;
		}
        
	public void join(Member member) {
		memberRepository.save(member);
		}
    
	public Member findMember(Long memberId) {
		return memberRepository.findById(memberId);
		}
}

해결 과정

  1. MemberServiceImpl 생성자 만들고 매개변수 받음 -> 이 매개변수로 구현체가 들어옴.
  2. Appconfig가 구현체 정해서 넣어줌 -> 구현체 넣는 과정 뭔데?
  3. Appconfig 클래스 안에 함수 만듦.
  4. return으로 구현체 반환.
예시

Appconfig appconfig = new Appconfig();
MemberService memberService = appconfig.memberService();

appconfig.memberService()의 반환 값은?
-> new MemberServiceImpl(new MemoryMemberRepository());

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();
		}
}

나아진 점 :
1. MemberRepository, DiscountPolicy 따로 뺌으로서 중복 제거
-> 원래 meberRepository 변경시 두 번 수정해야했음.
2. 흐름 파악에 유리해짐.

IoC, DI, 컨테이너

제어의 역전 IoC (Inversion of Control)

AppConfig 등장 전 : 구현체가 구현체를 생성, 연결, 실행까지 다 함. 구현체가 제어 흐름을 조종. 개발자 입장에서는 자연스러운 흐름.

AppConfig 등장 후 : 구현체는 자신의 로직 실행만을 담당함. 제어 흐름은 AppConfig가 담당.

-> 프로그램 제어 흐름을 직접 제어 X, 외부에서 관리 => 제어의 역전(IoC)

프레임워크 vs 라이브러리

  • 내 코드를 제어하며, 내가 그것에 맞춰야함 -> 프레임워크 (JUnit)
  • 내가 작성한 코드가 제어 흐름을 담당 -> 라이브러리

의존관계 주입 DI (Dependency Injection)

  • OrderServiceImplDiscountPolicy 인터페이스에 의존. 실제 어떤 구현체가 사용될 지는 모름.
  • 의존관계는 정적 클래스 의존관계, 동적 클래스 의존관계 가 있다.

정적 클래스 의존관계

애플리케이션 실행 안해도 알 수 있음.
예를들어, OrderServiceImplMemberRepository, DiscountPolicy에 의존함을 앎. 하지만 정확히 어떤 구현체가 주입 될지는 알 수 없음.

동적 클래스 의존관계

애플리케이션 실행 시점에 실제 생성된 객체 인스턴스의 참조가 연결된 의존 관계.

애플리케이션 실행 시점에 외부에서 구현체를 생성하고, 클라이언트와 서버의 실제 의존관계 연결 -> 의존관계 주입

IoC 컨테이너, DI 컨테이너

AppConfig 처럼 객체 생성, 관리하며 의존관계를 연결해 주는 것

  • 최근에는 주로 DI 컨테이너라 함.

스프링으로 전환

@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();
		}
}
  • @Configuration 붙여줌.
  • 각 메서드 @Bean 붙여줌 -> 스프링 컨테이너에 스프링 빈으로 등록.

사용해보기

	public static void main(String[] args) {
		// AppConfig appConfig = new AppConfig();
		// MemberService memberService = appConfig.memberService();
        
		ApplicationContext applicationContext = new
		AnnotationConfigApplicationContext(AppConfig.class);
		MemberService memberService = applicationContext.getBean("memberService", MemberService.class);
        }
  • ApplicationContext스프링 컨테이너라고 함.
  • 기존에는 AppConfig로 DI 했지만, 이젠 스프링 컨테이너 사용.
  • 스프링 컨테이너는 @Configuration 붙은 것을 설정 정보로 사용함. 그리고 @Bean 적힌 메서드 모두 호출하여 반환된 객체를 스프링 컨테이너에 등록함. 이렇게 스프링 컨테이너에 등록된 객체를 스프링 빈이라 함.
  • 스프링 빈 이름 : @Bean 붙은 메서드 이름 (memberService, orderService)
  • 이전에는 AppConfig로 직접 불러왔지만, 이젠 필요한 스프링 빈 찾아야함 -> applicationContext.getBean("스프링 빈 이름", 클래스명.class)
profile
저스트두잇

0개의 댓글