Spring 핵심 기본편

이승효

Updated:

Intro

Spring 핵심 기본 편을 듣고 알게 된 점을 공유합니다.

객체지향 설계

  • 요구사항
    • 할인 정책 서비스: VIP 회원 1000원 할인?? 10% 할인??? 고민..

할인 서비스를 만들어야 하는데 만약 고객사의 문제로 할인정책이 아직 확정이 되지 않을 경우
무작정 기다릴 것이 아니라 역할(인터페이스)과 구현(구현체)을 나눈 객체지향 설계로 개발을 시작하면 된다.

인터페이스를 먼저 만들고 구현체를 언제든지 갈아끼울 수 있도록 만들어 보자..

참고: 지금은 스프링 없는 순수한 자바로만 개발.

할인 정책 인터페이스(역할)

public interface DiscountPolicy {
	/*
	 * @return 할인 대상 금액
	 */
	int discount(Member member, int price);
}

먼저 할인 정책 인터페이스를 만들고

1000원 할인 구현체(구현)

@Component
public class FixDiscountPolicy implements DiscountPolicy {

	private int discountFixAmount = 1000;

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

이를 구현하는 1000원 할인 구현체와 10%할인 구현체를 만들어 줍니다.

10% 할인 구현체(구현)

public class RateDiscountPolicy implements DiscountPolicy {

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

주문 서비스 인터페이스(역할)

public interface OrderService {
	Order createOrder(Long memberId, String itemName, int itemPrice); // 주문생성
}

주문 서비스 구현체(구현)

public class OrderServiceImpl implements OrderService {
  private final MemberRepository memberRepository = new MemoryMemberRepository();
  private final DiscountPolicy discountPolicy = new FixDiscountPolicy();
  
@Override
	public Order createOrder(Long memberId, String itemName, int itemPrice) {
		Member member = memberRepository.findById(memberId); // 회원정보 조회
		int discountPrice = discountPolicy.discount(member, itemPrice);
		
		return new Order(memberId, itemName, itemPrice, discountPrice);
	}
 }

만들어진 구현체를 사용해 할인을 적용 시켜줍니다.

새로운 할인 정책 적용

public class OrderServiceImpl implements OrderService {

    //private final DiscountPolicy discountPolicy = new FixDiscountPolicy(); // 고정할인
    private final DiscountPolicy discountPolicy = new RateDiscountPolicy(); // 정률할인

}

새로운 할인 정책을 적용하려면 FixDiscountPolicy, RateDiscountPolicy 역할 별로 변경하면 된다. 작동하는 데는 지장이 없다.

하지만 이 코드는 객체지향 설계 5원칙 DIP와 OCP를 위반하는 코드이다.

  • DIP (Dependency inversion principle) 의존관계 역전 원칙

    • 프로그래머는 “추상화에 의존해야지, 구체화에 의존하면 안 된다.” 의존성 주입은 이 원칙을 따르는 방법 중 하나다.
    • 쉽게 이야기해서 구현 클래스에 의존하지 말고, 인터페이스에 의존하라는 뜻

현재 코드는 DiscountPolicy(추상)뿐만 아니라 FixDiscountPolicy, RateDiscountPolicy 구현 클래스에도 의존하고 있다.

  • OCP (Open/closed principle) 개방-폐쇄 원칙

    • 소프트웨어 요소는 확장에는 열려 있으나 변경에는 닫혀 있어야 한다.

또한 정책에 따라 FixDiscountPolicy, RateDiscountPolicy 코드를 변경해야하기 때문에 OCP도 위반하고 있다.

public class OrderServiceImpl implements OrderService {

    private final DiscountPolicy discountPolicy; // 정률할인, 고정할인

}

DIP와 OCP를 지키기 위해선 DiscountPolicy 인터페이스만 의존해야 하는데 이 문제를 해결하려면 DiscountPolicy의 구현 객체를 대신 생성하고 주입해 주어야 한다.

관심사의 분리

AppConfig 등장

구현 객체를 대신 생성하고 주입해 주기 위한 책임을 가지는 AppConfig 클래스를 만들어 봅니다.

public class AppConfig {

	public OrderService orderService() {
		return new OrderServiceImpl(memberRepository(), discountPolicy());
	}

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

	public MemberRepository memberRepository() {
        	return new MemoryMemberRepository();
    	}
}

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

    @Override
    public Order createOrder(Long memberId, String itemName, int itemPrice) {
        Member member = memberRepository.findById(memberId);
        int discountPrice = discountPolicy.discount(member, itemPrice);

        return new Order(memberId, itemName, itemPrice, discountPrice);
    }
}

AppConfig에서 실제 동작에 필요한 구현 객체를 생성해서 할인 정책에 관한 구현체를 생성자 주입을 통해 넣어 주면 DIP와 OCP위반 문제가 해결됩니다.

AppConfig 객체는 FixDiscountPolicy 객체를 생성하고 그 참조 값을 OrderServiceImpl을 생성하면서 생성자로 전달한다.

OrderServiceImpl 입장에서 보면 의존관계를 마치 외부에서 주입해 주는 것 같다고 해서 DI(Dependency Injection) 의존성 주입이라고 한다.

Leave a comment