-
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은 기능을 실행하는 책임만 지면 된다.
[출처 - 스프링 핵심 원리 - 기본편]
스프링 핵심 원리 - 기본편 - 인프런 | 강의
스프링 입문자가 예제를 만들어가면서 스프링의 핵심 원리를 이해하고, 스프링 기본기를 확실히 다질 수 있습니다., 스프링 핵심 원리를 이해하고, 성장하는 백엔드 개발자가 되어보세요! 📢
www.inflearn.com
'FrameWork > Spring&Spring-boot' 카테고리의 다른 글
Spring 기본 6 - 새로운 구조와 할인 정책 적용, 전체 흐름 정리 (0) 2023.11.09 Spring 기본5 - AppConfig 리팩토링 (0) 2023.11.09 Spring 기본3 - 객체 지향 원리 적용, OCP,DIP 위반의 문제 (1) 2023.11.09 Spring 기본2 - 주문 할인 도메인 설계 (1) 2023.11.07 Spring 기본1 - 회원 도메인 설계, SOLID 원칙 (0) 2023.11.07