Spring OCP, DIP 위반 및 AppConfig

강정우·2023년 10월 29일
0

Spring-boot

목록 보기
3/73
post-thumbnail

문제점

1. OCP 위반

할인 정책을 변경하려고 봤더니 클라이언트격인 OrderServiceImpl 코드를 고쳐야한다.
이것은 OCP를 위반한 것이다.

2. DIP 위반

그리고 또 위 코드를 자세히 살펴보면 인터페이스만 의존한 것 같지만 사실 new 예약어 오른쪽인 구현체에도 의존하고 있다.
즉, FixDiscountPolicy 구현체에서 RateDiscountPolicy 구현체로 바꾸는 순간 Impl의 코드를 거의 바꿔야한다는 것이다.

해결 (AppConfig)

AppConfig

public class AppConfig {
    public MemberService memberService() {
        return new MemberServiceImpl(new MemoryMemberRepository());
    }

    public OrderService orderService() {
        return new OrderServiceImpl(new MemoryMemberRepository(), new FixDiscountPolicy());
    }
}
  • 애플리케이션의 전체 동작 방식을 config하기 위한 구현체를 생성하고, 연결하는 책임을 갖는 별도의 설정 클래스를 이용하여 위 2 문제를 해결할 수 있다.

  • 즉, AppConfig가 애플리케이션의 실제 동작에 필요한 구현 객체를 생성한다.
    그리고 AppConfig가 생성한 객체 인스턴스의 래퍼런스를 생성자를 통해서 주입(연결)해준다.

  • 이렇게 되면 앞서 작성했던 클라이언트(~~Impl) 클래스에서는 의존관계에 대한 고민은 필요없고 실행에만 집중할 수 있게 된다.

생성자 주입

  • 그래서 위 코드를 보면 클라이언트(~~Impl)입장에서는 의존관계를 외부에서 주입해주는 것 같다고 하여 이것이 바로 DI (Dependency Injection) 우리말로 "의존관계 주입" 이라고 한다.

  • 이로서 DIP가 완성(구체클래스가 아닌 온전히 interface에만 의존하고있다.)이 되었다.

Test 환경에서

public class OrderServiceTest {
//    AppConfig appConfig = new AppConfig();
//    MemberService memberService = appConfig.memberService();
//    OrderService orderService = appConfig.orderService();

    MemberService memberService;
    OrderService orderService;

    @BeforeEach
    public void beforeEach() {
        AppConfig appConfig = new AppConfig();
        memberService = appConfig.memberService();
        orderService = appConfig.orderService();
    }

    @Test
    void createOrder() {
        Long memberId = 1L;
        Member member = new Member(memberId, "MemberA", Grade.VIP);
        memberService.join(member);

        Order order = orderService.createOrder(memberId, "Item A", 10000);
        Assertions.assertThat(order.getDiscountPrice()).isEqualTo(1000);
    }

}
  • 테스트 환경에서는 AppConfig에서 바로 꺼내쓰기보단 @BeforeEach 어노테이션을 활용하는 것이 좋다.

다른 Class에서

public class OrderApp {
    public static void main(String[] args) {
        AppConfig appConfig = new AppConfig();

//        MemberService memberService = new MemberServiceImpl();
        MemberService memberService = appConfig.memberService();
//        OrderService orderService = new OrderServiceImpl();
        OrderService orderService = appConfig.orderService();

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

        Order order = orderService.createOrder(memberId, "Item A", 10000);

    }
}
  • 그래서 위의 모든 코드에서 이제는 Interface에만 의존하여 OCP, DIP 및 SOLID 5원칙을 모두 지키게 되었다.

AppConfig refactoring

  • 제일 위에 작성된 코드를 보면 중복이 있고 또 역할에 따른 구현이 잘 안 보인다. 무슨말이냐

App"Config" 즉, 구성,설정 정보인데 위 코드를 봐도 위 사진처럼 한 눈에 들어오지 않는다는 것이다.
그래서 사실 이러한 역할들을 드러나게 작성하는 것이 중요하다.

  • mac : opt + cmd + m, window : ctrl + alt + m 메서드 리팩토링으로 따로 메서드를 추출하였다.
public class AppConfig {
    public MemberService memberService() {
        return new MemberServiceImpl(getMemberRepository());
    }

    private static MemoryMemberRepository getMemberRepository() {
        return new MemoryMemberRepository();
    }

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

    public DiscountPolicy discountPolicy() {
        return new RateDiscountPolicy();
//        return new FixDiscountPolicy();
    }
}
  • 그래서 위 코드만 보고 아 getMemberRepository() 가 실제 Impl에 들어갈 구현체구나! 를 알 수 있고
    나중에 로직이 바뀌어서 메서드가 바뀌어도 2,4번째 생성해주는 메서드만 바꾸면 된다.

  • 그래서 만약 할인 정책이 바뀌었다면 코드 딱 1줄만 바꾸면 되는 것이다.

객체 지향 설계의 5가지 원칙 적용

  • 참고로 위 코드에서는 SRP, DIP, OCP 3가지의 원칙이 적용되었다.

1. SRP (단일 책임 원칙)

  • 구현 객체를 생성하고 연결하는 책임은 AppConfig가 담당한다.
  • 클라이언트 객체(~impl)는 실행하는 책임만 담당한다.

2. DIP (의존관계 역전의 법칙)

  • 클라이언트 코드에서 DisCountPolicy 추상화 인터페이스만 의존하도록 하였다. 이때 클라이언트 코드는 인터페이스만으로는 아무것도 실행할 수 없으므로
  • AppConfig에서 --DiscountPolicy 객체 인스턴스를 클라이언트 코드 대신 생성하여 클라언트와 의존관계를 주입했다.

3. OCP

  • 위 코드에서는 다형성을 사용하고 클라이언트가 DIP를 지키고 있다.
  • 애플리케이션을 사용영역과 구성영역으로 나누었다.
  • 따라서 AppConfig의 의존관계를 바꿈으로써 클라이언트 코드에는 아무것도 변경이 일어나지 않는다. 즉, 소프트웨어 요소를 확장해도 사용 영역의 변경을 닫혀있다(변경할 필요가 없다).

reference

  • 스프링 핵심 원리 - 갓영한
profile
智(지)! 德(덕)! 體(체)!

0개의 댓글