Notice
Recent Posts
Recent Comments
Link
«   2025/01   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
Archives
Today
Total
관리 메뉴

개발자되기 프로젝트

관심사의 분리 본문

인프런/[인프런] Spring 핵심원리 이해

관심사의 분리

Seung__ 2021. 7. 25. 20:46
  • 애플리케이션은 하나의 공연
  • 인터페이스 = 역할(배역)
  • 그럼 배우 선택은 누가?? 배우선택은 배우가 하지 않는다.
  • 클라이언트가 인터페이스와 구현체 모두 의존했다.
    인터페이스 = new 구현체()
  • 즉, 다른 배우(클라이언트)가 다른 배역의 배우를 선택한 것과 같다.
  • 이상하지않음? 배우는 다른 배우가 정할 수 없고 기획자나 감독이 지정해야한다.
  • 이전 코드를 다시 보면 배우의 역할에 배우 선택까지 포함이 되었던 것이다.
  • 관심사 분리가 되지 않았던 것이다. 
  • 따라서 기획자를 만들어 배우, 감독의 책임을 화아아악실히 분리해야 한다.
  • 즉! 어떤 구현체를 사용할지 정하는 것은 클리이언트에서 하지 않는다!

1. 그럼 누가 구현체를 정하는데? AppConfig


  • 애플리케이션의 전체 동작 방식을 구성(config)하기 위해 
  • "구현 객체를 생성"하고 "연결"하는 "책임"을 가지는 별도의 설정 클래스가 필요하다.(스프링이 하던 역할...?) 

 

2. MemberServviceImpl 수정


  • MemberRepository도 클라이언트(MemberServiceImpl)에서 구현체를 선택했다. 
  • 생성자를 통해 구현체를 주입받도록 변경하자.
  • 자 이제 MemberServiceImpl은 DIP, OCP를 모두 만족한다.
    - DIP : 추상화에만 의존
    - OCP : 추상화에만 의존 하기 때문에 구현체를 변경해도 클라이언트에는 코드변경 없음.
    public class MemberServiceImpl implements MemberService{
    
        //private final MemberRepository memberRepository ;
        private MemberRepository memberRepository ;
    
        public MemberServiceImpl(MemberRepository memberRepository) {
            this.memberRepository = memberRepository;
        }​
  • 즉, MemberServiceImpl 인스턴스가 생성될 때 MemberRepository의 구현체가 주입된다.
  • 따라서 Appconfig에 아래와 같이 추가해 주자. 
    public class AppConfig {
    
        public MemberService memberService(){
            return new MemberServiceImpl(new MemoryMemberRepository());
        }
    
    }​
  • 즉, 다른데서 AppConfig를 통해 MemberService를 불러 쓸 때!!!!! MemoryMemberRepository가 들어간다.

 

2. OrderServiceImpl 수정


  • OrderServiceImpl은 사용하는 필드가 두개이다.
    - MemberRepository, DiscountPolicy
  • 둘 다 생성자 주입을 받자(참고로 final로 선언하면 기본으로 정해주던가 생성자 주입을 받아야한다.)
    public class OrderServiceImpl implements OrderService{
    
        private final MemberRepository memberRepository;
        //private final DiscountPolicy discountPolicy = new RateDiscountPolicy();
        private final DiscountPolicy discountPolicy; //이렇게하면 인터페이스에만 의존함.
    
        public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
            this.memberRepository = memberRepository;
            this.discountPolicy = discountPolicy;
        }
  • 자 이제 OrderServiceImpl은 DIP, OCP를 모두 만족한다.
    - DIP : 추상화에만 의존
    - OCP : 추상화에만 의존 하기 때문에 구현체를 변경해도 클라이언트에는 코드변경 없음.
  • AppConfig에 다음과 같이 추가해주자.
    public class AppConfig {
    
        public MemberService memberService(){
            return new MemberServiceImpl(new MemoryMemberRepository());
        }
        
        public OrderService orderService(){
            return new OrderServiceImpl(new MemoryMemberRepository(), new FixDiscountPolicy());
        }
    
    }​
  • 앞에서도 말했지만, MemoryMemberRepository()인스턴스를 각각 생성한다.
  • 그래서 둘이 다른 객체여서 참조값이 다르지 않을까? 라고 생각했다.
  • 하지만 데이터를 저장하기 위해 만든 Map이 static으로 선언되어 있다.
  • static은 인스턴스와 무관하게 클래스에 딱! 1개만 생성된다. 
  • 따라서 모든 인스턴스는 동일한 static 멤버변수를 공유해서 data저장이 다른곳에 될 일은 없다.

 

3. AppConfig의 책임


  • 구현 객체를 생성
  • 생성한 객체 인스턴스를 생성자를 통해 주입해줌 --> Injection

 

4. 변경된 클래스 다이어그램


 

 

5. 객체 인스턴스 다이어그램


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

 

6. 실행


 - MemberApp

  • AppConfig를 통해 memberService를 생성한다.
public class MemberApp {

    public static void main(String[] args) {

        AppConfig appConfig = new AppConfig();

        MemberService memberService = appConfig.memberService();
     //   MemberService memberService = new MemberServiceImpl();

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

        Member findMember = memberService.findMember(1L);
        System.out.println("new Member = " + member.getName());
        System.out.println("findMember = " + findMember.getName());

    }
}

- OrderApp

public class OrderApp {

    public static void main(String[] args) {

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

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

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

        System.out.println("Order : " + order.toString());
        System.out.println("Order.calculatedPrice : " + order.calculatedPrice());

    }
}

 

7. Test


  • 각 테스트는 독립적이어야 한다.
  • 각 테스트 실행 전 appConfig를통해 필요한 인스턴스 생성.
  • @BeforeEach활용
       MemberService memberService;
    
        @BeforeEach
        public void beforeEach(){
            AppConfig appConfig = new AppConfig();
            memberService = appConfig.memberService();
        }
    
        @Test
        void join(){​

모든 테스트 통과!

 

 

8. GitHub : 210725, Separation of concerns


 

GitHub - bsh6463/SpringCoreFunction

Contribute to bsh6463/SpringCoreFunction development by creating an account on GitHub.

github.com

 

Comments