ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 관심사의 분리
    FrameWork/Spring&Spring-boot 2021. 12. 30. 14:04

    -애플리케이션을 하나의 공연이라고 생각해보자. 각각 인터페이스를 배역이라 생각하자. 그런데! 실제 배역에 맞는 배우를 선택하는 것은 누가 하는가?

     

    -로미오와 줄리엣 공연을 하면 로미오 역할을 누가 할지 줄리엣 역학을 누가 할지는 배우들이 정하는게 아니다. 이전 코드는 마치 로미오 역할(인테페이스)을 하는 레오나르도 디카프리오(구현체 배우)가 줄리엣 역할(인터페이스)을 하는 여자 주인공(구현제,배우)을 직접 초빙하는 것과 같다. 디카프리오는 공연도 해야하고 도이세 여자 주인공도 공연에 직접 초빙해야 하는 *다양한책임*을 가지고 있다.

     

    관심사를 분리하자

    • 배우는 본인의 역할인 배역을 수행하는 것에만 집중해야 한다.
    • 디카프리오는 어떤 여자 주인공이 선택되더라도 똑같이 공연을 할 수 있어야 한다.
    • 공연을 구성하고, 담당 배우를 섭외하고, 역할에 맏는 배우를 지정하는 책임을 담담하는 별도의 *공연 기획자*가 나올 시점이다.
    • 공연기획자를 만들고, 배우와 공연 기획자의 책임을 확실히 분리하자

    AppConfig등장

    • 애플리케이션의 전체 동작 방식을 구성(config)하기위해,*구현 객체를 생성*하고*연결*하는 책임을 가지는 별도의 설정 클래스를 만들자
    public class AppConfig {
    
        public MemberService memberService(){
            return new MemberServiceImpl(new MemoryMemberRepository());
        }
    
        public OrderService orderService(){
            return new OrderServiceImpl( new MemoryMemberRepository(), new FixDiscountPolicy());
        }
    }

    생성자를 통해서, 이 MemberRepository에 무엇이 들어 갈지를 결정한다.

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

    -AppConfig를 통해서, memberService를 불러서 사용한다. 그러면, MemberServiceImpl 객체가 생성이 되는데,

    와 동시에 역시 새로 생성된 MemoryMemberRepository를 괄호에 집어 넣어준다.

     

    생성자 주입 : 생성자를 통해서 new 로 생성된 객체가 들어간다.

     

    -OrderServiceImpl 입장에서는 인터페이스만 지키면서 철저히 Dip원칙을 지키고 있다. 구체적인 클래스에 대해서 전혀 모른다. 누군가가 인터페이스의 구현체를 반들어서 생서자로 넣어 주고 있는 형태이다.

     

    AppConfig는 애플리케이션의 실제 동작에 필요한 *구현 객체를 생성*한다.

    • 'MemberServiceImpl'
    • 'MemoryMemberRpository'
    • 'OrderServiceImpl'
    • 'FixDiscountPolicy'

    AppConfig는 생성한 객체 인스턴스의 참조(레퍼런스)를 *생성자를 통해서 주입(연결)*해준다.

    • 'MemberServiceImpl' ->'MemoryMemberRpository'
    • 'OrderServiceImpl' ->'MemoryMemberRpository' , 'FixDiscountPolicy'

     

     

    MemberServiceImpl - 생성자 주입

    package hello.core.member;
    
    public class MemberServiceImpl implements MemberService {
    
        private MemberRepository memberRepository;
    
        public MemberServiceImpl(MemberRepository memberRepository) {
            this.memberRepository = memberRepository;
        }
    
    
        @Override
        public void join(Member member) {
            memberRepository.save(member);
        }
    
        @Override
        public Member findMember(Long memberId) {
            return memberRepository.findById(memberId);
        }
    }
    • 설계 변경으로 'MemberServiceImpl'은 'MemoryMemberRepository'를 의존하지 않는다!
    • 단지 'MemberRepository'인터페이스만 의존한다.
    • 'MemberServiceImpl'입장에서는 생성자를 통해 어떤 구현 객체가 들어올지(주입될지)는 알 수 없다.
    • 'MemberServiceImpl'의 생성자를 통해서 어떤 구현 객체를 주입할지는 오직 외부('AppConfig')에서 결정된다.
    • 'MemberServiceImpl'은 이제부터 *의존관계에 대한 고민은 외부*에 맡기고 *실헹에만 집중*하면 된다.

    그림-클래스 다이어그램

    • 객체의 생성과 연결은 'AppConfig'가 담당한다.
    • DIP완성: 'MemberServiceImpl'은 'MemberRepsitory'인 추상에만 의존하면 된다. 이제 구체 클래스를 몰라도 된다.
    • 관심사의 분리 : 객체를 생성하고 연결하는 역할과 실행하는 역학이 분리되었다.

    그림-회원 객체 인스턴스 다이어그램

    • 'appConfig'객체는 'memoryMemberRepository'객체를 생성하고 그 참조값을 'memberServiceImpl'을 생성하면서 생성자로 전달한다.
    • 클라이언트 'memberServiceImpl'입장에서 보면 의존관계를 마치 외부에서 주입해주는 것 같다고 해서                  DI(Dependency Injection)우리말로 의존관계 주입 또는 의존성 주입이라 한다.

    *OrderServiceImpl-생성자 주입*

    package hello.core.order;
    
    import hello.core.discount.DiscountPolicy;
    import hello.core.member.Member;
    import hello.core.member.MemberRepository;
    import hello.core.member.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);
        }
    }
    

     

    • 설계변경으로 'OderServiceImpl'은 'FixDiscountPolicy'를 의존하지 않는다.
    • 단지,'DiscountPolicy'인터페이스만 의존한다.
    • 'OrderServiceImpl'입장에서는 생성자를 통해 어떤 구현 객체가 들어올지(주입될지)는 알 수 없다.
    • 'OrderserviceImpl'의 생성자를 통해서 어떤 구현 객첼르 주입할지는 오직 외부('AppConfig')에서 결정한다.
    • 'OrderServiceImpl'는 이제 실행에만 집중하면 된다.

     

    • 'OrderServiceImpl'에는 'MemoryRepositry','FixDiscountPolicy'객체의 의존관계가 주입된다.

    *정리*

    • AppConfig를 통해서 관심사를 확실하게 분리했다.
    • 배역,배우를 생각해보자
    • AppConfig는 공연 기획자다.
    • AppConfig는 구체 클래스를 선택해야 한다. 배역에 맞는 담당 배우를 선택한다. 애플리케이션이 어떻게 동작할지 전체 구성을 책임진다.
    • 이제 각 배우들은 담당 기능을 실행하는 책임만 지면 된다.
    • 'OrderServiceImpl'은 기능을 실행하는 책임만 지면 된다.

    개발 팁

    @BeforeEach : 각 테스트가 실행 되기전에 무조건 실행되는 메서드에 다는 annotation

     

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

     

     

    [출처 : 김영한. 스프링 핵심 원리-기본편. 인프런]

     

    댓글

Designed by Tistory.