ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 스프링의 등장 - 순수 자바 코드에서 스프링까지 2
    Spring/Spring 2022. 1. 16. 23:22
    728x90
    반응형

     

     

     현재까지 작성한 코드에서 문제가 있다. 바로 OCP, DIP 위반이다.

    왜 OCP, DIP 위반일까? 지금 현재 FixDicountPolicy를 이용하였다. 근데 만약에 회사의 요구사항으로 RateDiscounPolicy 정책으로 바뀐다고 해보자. 그럼 어떻게 될까?

     

     새로 RateDicountPolicy 정책으로 할인 정책을 바꿀때 OCP, DIP 위반이 발생한다. 다음 코드를 보자.

    public class OrderServiceImpl implements OrderService{
    
        private final MemberRepository memberRepository = new MemoryMemberRepository();
        //private final DiscountPolicy discountPolicy = new FixDiscountPoliscy();
        private final DiscountPolicy discountPolicy = new RateDiscountPolicy();
    	
        ....
        
    }

     

     이상한점을 발견했는가? 주문 서비스 클라이언트인 OrderServiceImpl 중

    private final DiscountPolicy discountPolicy = new RateDiscountPolicy(); 이 부분이 문제가 된다.

     

    OrderServiceImpl 객체는 분명히 DiscountPolicy 인터페이스(역할)에 의존하고 있지만 구현 클래스에도 의존하고 있다는 점이다. new 연산자로 받은 뒷부분은 무엇인가? 바로 구현 클래스인 RateDiscountPolicy 이다. 결국 할인 정책을 바꾸기 위해 OrderServiceImpl에서 코드 수정이 이루어져야한다는 점이다. 이 부분 때문에 OCP, DIP에 위반이 되는 것이다. 

     

    역할과 구현 모두 의존하여 OCP, DIP에 위반
    구현(구체) 클래스에 의존 - DIP 위반

    클라이언트 코드에 변경이 발생 - OCP 위반

     


     

      이 문제를 해결하기 위해 어떻게 해야 할까?

     

     누군가가 클라리언트인 OrderServiceImpl 객체에 DiscountPolicy의 구체 클래스를 대신 생성하고 주입해줘야한다.

    여기에서 나온것이 바로 설정 클래스이다. 

     

    AppConfig의 등장

     애플리케이션의 전체 동작 방식을 구성하기 위해 구현(구체) 객체를 생성하고, 연결하는 책임을 갖는 별도의 설정 클래스이다. AppConfig에서 구현 객체 생성, 구현한 객체를 필요한 곳에 주입하는 역활을 수행한다.

     

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

     

     

    AppConfig의 설정으로 해당 클라이언트 OrderServiceImpl은 더이상 구현(구체) 클래스를 의존하지 않게 된다.

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

     

     

     이로써 OrderServiceImpl은 인터페이스(역할)만 의존하게 되어 DIP 원칙이 성립되고, 또한 구현 객체들을 바꿔도 해당 클라이언트는 코드의 변경이 없게 되는 OCP 원칙이 성립하게 된다. 때문에 OrderServiceImpl은 해당 로직을 실행만 하면 되는 단일 책임 원칙(SRP)에도 성립하게 된다.

     

     위와 같은 관계를 보면 의존 관게를 마치 외부에서 주입해주는 것 같다고 해서 DI, 우리말로 의존 관계 주입 또는 의존성 주입이라 한다.

     

     

    AppConfig 리펙터링

     리펙터링이란 결과의 변경 없이 코드의 구조를 재조정을 하는 것을 말한다. 작성한 AppConfig를 코드의 구조를 재조정해보자.

     

     AppConfig를 리펙터링하기 위해 코드를 보면 중복이 있고, 역활에 따른 구현이 잘 안보인다. 

    무슨말일까?

     

    우선 코드를 보자

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

     이상한 점을 발견했는가?

    설계에 대한 정보가 안보인다. 

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

     이 부분만 보면 어떤 역할이 존재하는지 모른다. 그 이유는 구현체가 직접 주입되기 때문이다. MeberServiceImpl이 어떤 인터페이스(역할)을 주입을 받는지 모르게 된다. 즉, MemoryMemberRepository를 한번더 들여다 봐야 이게 어떤 인터페이스의 해당하는 구현(구체)클래스인지 알게 된다. 그러므로 AppConfig만 보고 한번에 어떤 구조인지 알 수 있도록 해당 인터페이스을 주입하여 MeberServiceImpl이 어떤 역할을 가진 인터페이스를 주입받는지 한눈에 알아 볼 수 있게 리펙토링한다.

     

     위 문제를 해결하면 중복 코드의 문제도 해결이 된다.

     

    public class AppConfig {
    
        public MemberService memberService(){
            return new MemberServiceImpl(memoryRepository());
        }
    
        private MemberRepository memoryRepository() {
            return new MemoryMemberRepository();
        }
    
        public OrderService orderService(){
            return new OrderServiceImpl(memoryRepository(), discountPolicy());
        }
    
        public DiscountPolicy discountPolicy(){
            return new FixDiscountPolicy();
        }
    }

     

     위와 같이 리펙토링을 하게 되면 역할에 따른 구현이 잘보이게 된다

    • MeberServiceImpl은 memoryRepository()를 통해 MemberRepository 인터페이스를 주입받는다.
    • MemoryRepository는 MemoryMemberRepository 구현 객체이다.

    • OrderServiceImpl는 memoryRepository(), discountPolicy()를 통해
      MemberRepository와 DiscountPolicy 인터페이스를 주입받는다.
    • MemberRepository는 MemorymemberRepository, FixDiscountPolicy 구현 객체이다.

     

     

     

     


    새로운 할인 정책의 등장

     

     새로운 할인 정책의 등장으로 이 할인 정책을 사용한다고 가정해보자. 코드의 변경은 어떻게 일어날까?

     

    우리는 위와 같은 AppConfig를 사용하여 애플리케이션이 실행되는 영역객체를 생성하고 구성하는 영역으로 분리하였다. 때문에 새로운 할인 정책으로 변경한다하더라도 우리는 객체를 생성하고 구성하는 AppConfig에서 해당 할인 정책만 변경해주면 된다. OrderserviceImpl은 그냥 실행만 해주면 되는 영역이므로 변경이 필요가 없다.

     

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

     

    이 부분만 변경해주면 끝난다. 이 한줄 변경이 어떻게 되는지 한번 그림으로 보자

     

      이와 같이 AppConfig를 이용하게 되면 구성 영역만 영향을 받고, 사용 영역은 코드가 전혀 변경하지 않아도 된다.

    이와같이 우리는 AppConfig를 이용하여 OCP, DIP, SRP 모두 만족하게 되는 코드를 구현할 수 있게 되었다.

     

     

    사용 영역의 객체들은 실행만 하면 된다. - SRP(단일 책임 원칙) 성립
    구체 클래스에 더이상 의존하지 않고 인터페이스만 의존 - DIP(의존 관계 역전) 성립
    확장에는 열려 있으니 변경에는 닫혀 있다 - OCP(개방/페쇄 원칙) 성립

     

     OCP 개념이 좀 헷갈렸는데 여기서 제대로 알게된것같다. 

     

    ▶ 확장에 열려있다 -> 새로운 할인 정책으로 변경

    ▶ 변경에는 닫혀있다 -> 실행을 담당하는 사용 영역의 코드 변경이 필요 없다

     

     

     

     

     

    728x90
    반응형

    댓글

Designed by Tistory.