Basic#2 OCP/DIP

17236 단어 SpringSpring

2. OCP/DIP

Spring framework가 등장하게된 계기에 대해서 살펴보고자 한다. 우선 이번 section에서는 spring framework를 사용하지 않고 순수 자바로만 구현했을 때 상황을 예제를 통해 살펴본다.

1. 순수 Java로 구현한 서비스

  • ISP 원칙을 지키기 위해서 인터페이스와 구현체를 분리한다.
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) {
    	...
    }

MemberRepositoryDiscountPolicy도 인터페이스이며, 이를 각각 구현한 것이 MemoryMemberRepositoryFixDiscountPolicy다.

상황1 : 할인률 정책 변경

기존 정액 할인율(FixDiscountPolicy)에서 정률 할인율(RateDiscountPolicy)로 비즈니스 기획이 변경되었다고 가정한다. 앞서 ISP 원칙을 지켰기 때문에 RateDiscountPolicy를 새로 개발하는 것은 쉬운 일이다. 하지만 기존 할인 정책을 사용하는 로직을 모두 변경해줘야 한다.

이는 수정에 있어서 많은 번거로움과 의도치 않는 버그를 발생시킬 수 있다.

public class OrderServiceImpl implements OrderService {

	private final MemberRepository memberRepository = new MemoryMemberRepository();
    // private final DiscountPolicy discountPolicy = new FixDiscountPolicy();
    private final DiscountPolicy discountPolicy = new RateDiscountPolicy();
    
    ...
  • OCP 위배 : 확장에는 열려있으나 수정에는 닫혀있어야 한다 → 수정이 필요
  • DIP 위배 : 인터페이스에 의존해야하고 구현체에는 의존하지 말아야한다. → new를 통해 구현체와 의존 관계가 발생

2. 그럼 어떻게 Mapping?

위의 문제를 보고 그럼 어떻게 인터페이스와 구현체를 분리하면서 이들을 mapping할 수 있을까?라는 생각이 들 것이다.

이때 SRP를 고려하여 문제를 해결한다. (SRP : 하나의 클래스는 하나의 책임)
즉, AppConfig 파일을 통해서 DI를 담당하는 구현체를 만든다. 그리고 각각의 구현체는 인터페이스만 알게 한다. (OCP/DIP 준수)

public class AppConfig {
	
    public MemberService memberService() {
    	return new MemberServiceImpl(memberRepository());
    }
    
    public OrderService orderService() {
        return new OrderServiceImpl(memberRepository(), discountPolicy());
    }

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

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

각각의 구현체에서는 new를 통해 직접 할당하지 않고, 인터페이스만 선언하여 사용한다.

public class OrderServiceImpl implements OrderService{
//    private MemberRepository memberRepository = new MemoryMemberRepository();
//    private DiscountPolicy discountPolicy = new FixDiscountPolicy();
//    private DiscountPolicy discountPolicy = new RateDiscountPolicy();
    private MemberRepository memberRepository;
    private DiscountPolicy discountPolicy;

만약 상황1이 다시 발생한다면, 해당 할인 정책에 대한 구현체를 AppConfig 파일에서 mapping시켜주면 다른 구현체의 수정없이 동작할 수 있다.

따라서, SRP, OCP, DIP, ISP를 모두 만족하면서 개발을 할 수 있다. (=객체지향의 특성을 모두 살릴 수 있다.)

3. 정리

AppConfig가 기존 개발자가 제어하던 흐름을 담당하여 처리하기 때문에 제어의 역전이 발생하며 IoC 컨테이너 또는 DI 컨테이너 라고 불린다. 이 AppConfig가 확장된 것이 결국은 Spring framework다.

물론 spring framework로 전환하려면 일부 annotation이 추가된다.

@Configuration
public class AppConfig {

    @Bean
    public MemberService memberService() {
        return new MemberServiceImpl(memberRepository());
    }

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

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

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

}

또한 main에서는 각 구현체를 Bean으로 가져와서 사용하면 된다.

public class MemberApp {
    public static void main(String[] args) {
        // Java
        // AppConfig appConfig = new AppConfig();
        // MemberService memberService = appConfig.memberService();

        // Spring framework
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
        MemberService memberService = applicationContext.getBean("memberService", MemberService.class);

        Member member = new Member(1L, "memberA", Grade.VIP);
        memberService.join(member);

        Member findMember = memberService.findMember(member.getId());

        System.out.println("find member = "+ findMember.getName());
   }
}

Annotation이 붙으면서 뭔가 더 추가된 듯 하지만 그 이점에 대해서는 다음 장에서 알아본다.


🛠 계속 업데이트 필요
2022.03.25 최초 작성

좋은 웹페이지 즐겨찾기