SPRING 핵심 원리 [ 기본편 ]

[스프링 핵심 원리] 섹션 3.3 객체 지향 원리 적용 (관심사의 분리)

wlalsu_u 2023. 1. 31. 13:30

3.3.1  관심사의 분리

 

 

 

어플리케이션을 공연, 인터페이스를 배우, 남자 주인공, 여자주인공을 구현체라고 가정해보자.

 

로미오와 줄리엣 공연을 한다고 가정하면, 남자 주인공(구현체) 를 결정하는 것은 기획자의 역할이다.

 

 

하지만, 이전 코드는 여자 주인공(구현체) 를 남자 주인공(구현체) 가 직접 초빙하는 것과 같다.

 

 

 

즉, 남자 주인공 (구현체) 는 공연을 하고, 여자 주인공을 초빙해야 하는 다양한 책임을 갖게 된다.

 

 

 

 

 

이러한 문제는, 다음과 같은 관심사의 분리를 통해 해결할 수 있다.

 

 

 

 

 

 

관심사의 분리

 

 

1) 배우는 배역을 수행하는 것에만 집중해야 한다. (한가지 책임)

 

2) 공연을 구성하고 기획하는 별도의 공연 기획자가 필요하다.

 

3) 공연 기획자를 만들고, 배우와 공연 기획자의 관심사를 분리하여야 한다.

 

 

 

 

 


 

 

3.3.2  AppConfig 의 등장

 

 

 

 

어플리케이션에서도 전반의 운영을 담당하는, 공연 기획자가 필요하다.

 

구현 객체를 생성하고, 연결해주는 일을 수행하는 AppConfig 클래스를 작성해보자.

 

 

 

 

src  >  main  >  java  >  hello.core 패키지에,

 

AppConfig 클래스를 생성하고 다음과 같이 작성한다.

 

 

 

 

 

package hello.core;
import hello.core.discount.FixDiscountPolicy;
import hello.core.member.MemberService;
import hello.core.member.MemberServiceImpl;
import hello.core.member.MemoryMemberRepository;
import hello.core.order.OrderService;
import 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());
 }
}

 

 

 

public MemberService memberService()

 

 

: MemberServiceImpl 구현 객체의 생성자를 통해, MemoryMemberRepository 를 주입(연결) 해준다.

 

 

 

public OrderService orderService()

 

 

: OrderServiceImpl 구현 객체의 생성자를 통해,

 

   MemoryMemberRepository와 FixDiscountPolicy 를 주입(연결) 해준다.

 

 

 

 

 

 


 

 

 

3.3.3  MemberServiceImpl 생성자 주입

 

 

 

 

객체 인스턴스 레퍼런스를 생성자를 통해 주입하였으므로,

 

각 클래스에 생성자를 작성해보자.

 

 

 

 

먼저, MemberServiceImpl 클래스 코드를 다음과 같이 작성한다.

 

 

 

package hello.core.member;
public class MemberServiceImpl implements MemberService {
 private final MemberRepository memberRepository;
 public MemberServiceImpl(MemberRepository memberRepository) {
 this.memberRepository = memberRepository;
 }
 public void join(Member member) {
 memberRepository.save(member);
 }
 public Member findMember(Long memberId) {
 return memberRepository.findById(memberId);
 }
}

 

 

 

private final MemberRepository memberRepository

 

 

: 구현체 없이 MemberRepository 를 생성만 한다.

 

 

 

public MemberServiceImpl(MemberRepository memberRepository)

 

 

: 생성자를 통해 차후 AppConfig 에서 구현체를 파라미터로 주입받는다.

 

 

 

 

 

 

 

 

위와 같이 코드를 변경하면,


MemberServiceImpl 은 MemoryMemberRepository 구현체를 의존하지 않고,

 

MemberRepository 인터페이스(추상화)에만 의존하게 된다.

 

 

 

 

즉, MemberServiceImpl 은 생성자를 통해 어떤 구현 객체가 주입될지 알 수 없고(의존관계는 AppConfig 결정),
오직 실행에만 집중할 수 있다.

 

 

 

 

이를 클래스 다이어그램으로 나타내면 다음과 같다.

 

 

 

 

 

출처 : 김영한 - spring 핵심 원리 - 기본편

 

 

 

 

 

 

1) AppConfig 가 객체의 생성과 연결을 담당한다. (관심사의 분리)

 

2) MemberServiceImpl 은 MemberRepository 인터페이스에만 의존한다. ( DIP 성립)

 

 

 

 


 

 

 

3.3.4  OrderServiceImpl 생성자 주입

 

 

 

 

OrderServiceImpl 도 동일하게,

 

객체 인스턴스 레퍼런스를 생성자를 통해 주입하였으므로, 생성자를 작성해보자.

 

 

 

OrderServiceImpl 코드를 다음과 같이 변경한다.

 

 

 

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

 

 

 

private final MemberRepository memberRepository / discountPolicy

 

 

: OrderService 는 사용하는 필드가 2개이므로, 구현체 없이 2개의 필드를 생성한다.

 

 

 

public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy)

 

 

: 생성자를 통해 차후 AppConfig 에서 구현체를 파라미터로 주입받는다.

 

 

 

 

 

 

 

위와 같이 코드를 변경하면,


OrderServiceImpl 은 FixMemberRepository 구현체를 의존하지 않고,

 

DiscountPolicy 인터페이스(추상화)에만 의존하게 된다.

 

 

 

 

즉, 동일하게 OrderServiceImpl 에 어떤 구현 객체를 주입할지는 AppConfig 가 결정하며,
OrderServiceImpl은 오직 실행에만 집중할 수 있다.

 

 

 

 


 

 

 

3.3.5  AppConfig 실행

 

 

 

 

위에서 작성한 코드를 실행해보자.

 

 

 

MemberApp 클래스

 

 

 

package hello.core;
import hello.core.member.Grade;
import hello.core.member.Member;
import hello.core.member.MemberService;
public class MemberApp {
 public static void main(String[] args) {
 AppConfig appConfig = new AppConfig();
 MemberService memberService = appConfig.memberService();
 Member member = new Member(1L, "memberA", Grade.VIP);
 memberService.join(member);
 Member findMember = memberService.findMember(1L);
 System.out.println("new member = " + member.getName());
 System.out.println("find Member = " + findMember.getName());
 }
}

 

 

 

1) AppConfig appConfig = new AppConfig()

 

: AppConfig 객체를 생성한다.

 

 

 

2) MemberService memberService = appConfig.memberService()

 

: 기존에는 MemberApp 에서 직접 MemberService 객체를 생성하였지만, 

 

   appConfig 에서 결정을 하도록 한다.

 

 

 

 

이외의 나머지 로직은 이전 코드와 동일하다.

 

 

 

 

 

 

 

 

OrderApp 클래스

 

 

 

package hello.core;
import hello.core.member.Grade;
import hello.core.member.Member;
import hello.core.member.MemberService;
import hello.core.order.Order;
import hello.core.order.OrderService;
public class OrderApp {
 public static void main(String[] args) {
 AppConfig appConfig = new AppConfig();
 MemberService memberService = appConfig.memberService();
 OrderService orderService = appConfig.orderService();
 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);
 }
}

 

 

 

OrderApp 의 코드도 MemberApp 과 동일하게 작성한다.

 

 

 

 

1) AppConfig appConfig = new AppConfig()

 

: AppConfig 객체를 생성한다.

 

 

 

2) MemberService memberService / OrderService = appConfig.memberService() / OrderService()

 

: 기존에는 MemberApp 에서 직접 MemberService 와 OrderService 객체를 생성하였지만, 

 

   appConfig 에서 결정을 하도록 한다.

 

 

 

 

 

 

 

 

MemberServiceTest 코드 수정

 

 

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

 

 

 

1) @BeforeEach

 

: 테스트를 실행하기 전에 호출된다. 

 

 

 

2) AppConfig appConfig = new AppConfig()

 

: AppConfig 객체를 생성한다.

 

 

 

3) MemberService memberService = appConfig.memberService()

 

:  appConfig 를 통해 MemberService 객체를 결정하도록 한다.

 

 

 

 

 

 

OrderServiceTest 코드 수정

 

 

MemberServiceTest 코드와 동일하다.

 

 

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

 

 

 

 

 

 

+ 정리 )

1) AppConfig 를 통해 관심사를 분리하였다.
2) AppConfig 는 어플리케이션의 전체 동작을 구성하는 역할이다.
3) 각 구현체 (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