ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Spring 기본 4 - 관심사의 분리
    FrameWork/Spring&Spring-boot 2023. 11. 9. 13:01

    관심사의 분리

    *관심사를 분리하자*

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

     

    AppConfig 등장

    • 애플리케이션 전체 동작 방식을 구성(config)하기 위해 구현 객체를 생성 하고, 연결 하는 책임을 가지는 별도의 설정 클래스를 만들자

    *AppConfig*

    package com.hello.core;
    
    import com.hello.core.discount.FixDiscountPolicy;
    import com.hello.core.discount.RateDiscountPolicy;
    import com.hello.core.member.MemberService;
    import com.hello.core.member.MemberServiceImpl;
    import com.hello.core.member.MemoryMemberRepository;
    import com.hello.core.order.OrderService;
    import com.hello.core.order.OrderServiceImpl;
    
    public class AppConfig {
    
        public MemberService memberService(){
            return new MemberServiceImpl(new MemoryMemberRepository());
        }
    
        public OrderService orderService(){
            return new OrderServiceImpl(new MemoryMemberRepository(), new FixDiscountPolicy());
        }
    }

    기존에 코드에서 MemberServiceImpl에서 MemberRepository를 사용할 일이 있으면 선언된 MemberRepository가 MemoryMemberRepository를 사용할지 DbMemberRepository를 사용할지 MemberServiceImple에 직접 코드로 작성해 주었지만 이제는 그렇게 하지않고 어떤 구체 구현클래스를 사용할지는 AppConfig에서 정해준다.

     

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

    우선 MemberServiecImpl자제를 Appconfig에서 생성해준다.

     

    AppConfig.java

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

     

    MemberServiceImpl.java

    public class MemberServiceImpl implements  MemberService{
        //MemberService에 선언된 변수는 memberRepository는
        private  MemberRepository memberRepository;
    
        public MemberServiceImpl(MemberRepository memberRepository){
            this.memberRepository = memberRepository;
        }
    
    }

     

    MemberServiceImp코드에도 생성자를 만들어서  memberRepository에 어떤 구체 클래스가 들어갈지 외부에서 결정할 수 있도록 생성자를 만들어준다. 그럼 결과 적으로 MemberServiceImpl에는 MemoryMemberRepository에 대한 어떤 것도 첨가되지 않게 된다. 즉 MemberServiceImpl은 추상(인터페이스)인 MemberRepository에만 의존하는 상태가 되어  DIP 원칙을 준수하는 상태가 된다.

     

    이러한 과정을 구체 구현 객체(인스턴스)가 생성자를 통해들어간다고해 *생성자 주입*이라고한다.

     

    위와 같이 어떤 구체 구현체를 사용할지 AppConfig에서 결정해주는 구조의 코드를 작성하면 어디선가 AppConfig 클래스를 불러서 memberServie()메서드를 사용할 것이다.

     

    • AppConfig는 애플리케이션 실제 동작에 필요한 구현 객체를 생성한다.
      • MemberServiceImpl
      • MemoryMemberRepository
      • OrderServiceImpl
      • FixDiscountPolicy
    • AppConfig는 생성한 객체 인스턴스의 참조(레퍼런스)를 생성자를 통해서 주입(연결),Injection해준다.
      • MemberServiceImpl → MemoryMemberRepository
      • OrderServiceImpl → MemoryMemberRepository,FixDiscountPolicy

     

    MemberServiceImpl,OrderServiceImpl - 생성자 주입

    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);
        }
    }
    package com.hello.core.order;
    
    import com.hello.core.discount.DiscountPolicy;
    import com.hello.core.discount.FixDiscountPolicy;
    import com.hello.core.member.Member;
    import com.hello.core.member.MemberRepository;
    import com.hello.core.member.MemoryMemberRepository;
    
    public class OrderServiceImpl implements OrderService{
    
        private MemberRepository memberRepository;
        private  DiscountPolicy discountPolicy;
    
        public OrderServiceImpl(MemberRepository memberRepository,DiscountPolicy discountPolicy){
            this.memberRepository = memberRepository;
            this.discountPolicy = discountPolicy;
        }
    
    
        @Override
        public Order createOrder(Long memberId, String itemName, int itemPrice) {
            //1. 회원 정보 조회
            Member member = memberRepository.findById(memberId);
            //2.할인에 대한 책임,역할은 discountPolicy에 위임
            int discountPrice = discountPolicy.discount(member, itemPrice);
    
            return new Order(memberId,itemName,itemPrice,discountPrice);
        }
    }
    • 설계 변경으로 MemberServiceImp과 OrderServiceImpl은 MemoryMemberRepository에 의존하지 않는다.
    • 단지 MemberRepository 인터페이스에만 의존한다.
    • MemberServiceImp과 OrderServiceImpl 입장에서 생성자를 통해 어떤 객체(인스턴스)가 들어올지(주입될지)는 알 수 없다.
    • MemberServiceImp과 OrderServiceImpl의 생성자를 통해서 어떤 구현 객체(인스턴스)를 주입할지는 오직 외부(AppConfig)에서 결정된다.
    • MemberServiceImp과 OrderServiceImpl은 이제부터 의존관계에 대한 고민은 외부에 맡기고 실행에만 집중 하면된다.

     

    클래스 다이어그램

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

     

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

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

     

    정리

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

     

    [출처 - 스프링 핵심 원리 - 기본편]

    https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8

     

    스프링 핵심 원리 - 기본편 - 인프런 | 강의

    스프링 입문자가 예제를 만들어가면서 스프링의 핵심 원리를 이해하고, 스프링 기본기를 확실히 다질 수 있습니다., 스프링 핵심 원리를 이해하고, 성장하는 백엔드 개발자가 되어보세요! 📢

    www.inflearn.com

     

    댓글

Designed by Tistory.