[Spring]스프링 핵심 원리(기본편) - 3. 스프링 핵심 원리 이해(2)

Wooyong Jung·2023년 9월 11일
0
post-thumbnail
post-custom-banner
  • 해당 게시물은 인프런 "스프링 핵심 원리 - 기본편" 강의를 참고하여 작성한 글입니다.
  • 자세한 코드 및 내용은 강의를 참고해 주시길 바랍니다.
    강의링크 -> 스프링 핵심 원리 - 기본편 (김영한)

Section3. 스프링 핵심 원리 이해 - 객체 지향 원리 적용

  • 새로운 할인 정책을 개발하고 OCP, DIP 같은 객체지향 설계 원칙을 충실히 준수하는지 확인합니다.
  • 객체지향 설계 원칙을 준수하기 위해 AppConfig 파일을 작성합니다.
  • IoC, DI, 컨테이너에 대해 알아봅니다.

📄 새로운 할인 정책 개발

서비스 오픈 직전에 할인 정책을 지금처럼 고정 금액 할인이 아니라 좀 더 합리적인 주문 금액당 할인하는 정률% 할인으로 변경하고 싶어요. 
예를 들어서 기존 정책은 VIP가 10000원을 주문하든 20000원을 주문하든 항상 1000원을 할인했는데, 
이번에 새로 나온 정책은 10%로 지정해두면 고객이 10000원 주문시 1000원을 할인해주고, 20000원 주문시에 2000원을 할인해주는 거에요.

새로운 요구사항이 주어졌습니다. 기존의 고정 할인 정책에서 비율 할인 정책으로 변경해야 하는데 우리는 DiscountPolicy 인터페이스를 이미 만들어놨으므로 구현체만 개발하면 되겠네요. 구현체 클래스명은 RateDiscountPolicy로 하겠습니다.

public class RateDiscountPolicy implements DiscountPolicy {
	private int discountPercent = 10; //10% 할인

	@Override
	public int discount(Member member, int price) {
		if (member.getGrade() == Grade.VIP) {
			return price * discountPercent / 100;
		} else {
    		return 0;
		}
	}
}

📄 새로운 할인 정책 적용과 문제점

RateDiscountPolicy 구현체를 작성했으니 새로운 할인 정책을 애플리케이션에 적용해 보겠습니다.


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

새로운 할인 정책을 적용하기 위해서는 OrderServiceImpl 클래스의 코드를 변경해야 하는데 여기서 2가지 문제점을 발견할 수 있습니다.

=> DIP 위반: OrderServiceImpl은 DiscountPolicy 뿐 아니라 RateDiscountPolicy에도 의존하고 있다
=> OCP 위반: 코드의 기능을 확장하는데 OrderServiceImpl에 영향을 준다

이를 해결하기 위해 OrderServiceImpl이 인터페이스에만 의존하도록 의존관계를 변경해주면 됩니다.

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

그리고 누군가 대신 OrderServiceImplDiscountPolicy의 구현 객체를 생성하고 주입해주어야 할 것 같네요.


📄 관심사의 분리 - 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();
    }
}

구현 객체를 생성하고, 연결하는 책임을 가지는 별도의 설정 클래스 AppConfig입니다. AppConfig는 구현 객체를 생성하고, 생성한 객체 인스턴스의 레퍼런스를 생성자를 통해서 주입합니다.

public class MemberServiceImpl implements MemberService {
	private final MemberRepository memberRepository;
	public MemberServiceImpl(MemberRepository memberRepository) {
		this.memberRepository = memberRepository;
	}
		...   
}

MemberServiceImpl는 이제 생성자를 통해서 구현체를 주입받습니다.


MemberServiceImpl은 이제 MemberRepository에만 의존하게 되었으므로 DIP 를 준수하게 되었습니다. 또, 기능 확장시 AppConfig만 수정하면 되므로 OCP 도 준수하게 되었네요.

public class AppConfig {
		...
        
    public DiscountPolicy discountPolicy() {
        // return new FixDiscountPolicy();
        return new RateDiscountPolicy();
    }
}

이제 코드의 변경없이 AppConfig의 수정만으로 기능을 확장할 수 있습니다.


📄 IoC, DI, 그리고 컨테이너

IoC (Inversion of Control)

  • 프로그램의 제어 흐름을 직접 제어(초반에 OrderServiceImplDiscountPolicy 직접 생성한 후 구현체 주입)하는 것이 아니라 외부 (AppConfig 파일)에서 관리하는 것

DI (Dependency Injection)

  • 애플리케이션 실행 시점에 외부에서 실제 구현 객체를 생성하고 클라이언트에 전달해서 클라이언트와 서버의 실제 의존관계가 연결 되는 것
  • 정적인 클래스 의존관계, 동적인 객체 인스턴스 의존관계

컨테이너

  • AppConfig 처럼 객체를 생성하고 관리하면서 의존관계를 연결해 주는 것을 IoC 컨테이너 또는 DI 컨테이너라 한다

📄 스프링으로 전환하기

지금까지 순수 자바 코드로 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 FixDiscountPolicy();
        return new RateDiscountPolicy();
    }
}

@Configuration : AppConfig에 설정을 구성
@Bean : 스프링 컨테이너에 스프링 빈으로 등록

public class MemberServiceTest {

    MemberService memberService;

    @BeforeEach
    public void beforeEach() {
//        AppConfig appConfig = new AppConfig();
//        memberService = appConfig.memberService();
        ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
        memberService = ac.getBean("memberService", MemberService.class);
    }

    @Test
    void join() {
    	...
    }

이제부터는 AppConfig를 통한 직접 조회가 아닌 스프링 컨테이너를 통해서 필요한 스프링 빈을 찾아야 합니다. 스프링 빈은 ac.getBean() 메서드를 사용해서 찾을 수 있습니다.


자바 코드를 스프링으로 전환했지만 뭔가 코드만 복잡해진 것 같고 크게 달라진 점을 찾기 어렵습니다. 다음 시간부터 스프링 컨테이너를 사용했을 때의 장점에 대해 알아보겠습니다.

profile
실패를 두려워하지 않는 백엔드 개발자가 되기 위해 노력하고 있습니다.
post-custom-banner

0개의 댓글