You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
publicclassMemberServiceTest {
MemberServicememberService;
@BeforeEach// 각 test 실행 전 무조건 실행publicvoidbeforeEach() {
AppConfigappConfig = newAppConfig();
memberService = appConfig.memberService();
}
publicOrderServiceorderService() {
returnnewOrderServiceImpl(memberRepository(), discountPolicy()); //여기 변경
}
publicDiscountPolicydiscountPolicy(){
returnnewFixDiscountPolicy();
}
역할과 구현이 한눈에 들어오게 표현이 된다.
중복을 제거하고 MemoryMemberRepository를 다른 구현체로 변경할때 한 부분만 변경하면 된다.
AppConfig를 보면 역할과 구현 클래스가 한눈에 들어온다.
애플리케이션 전체 구성이 어떻게 되어있는지 빠르게 파악할 수 있다.
새로운 구조와 할인 정책 적용
FixDiscountPolicy -> RateDiscountPolicy
AppConfig의 등장으로 애플리케이션이 크게 사용 영역과 객체를 구성하는 영역으로 분리된다.
할인 정책을 변경한다면 어디가 영향을 받을까 ?
: AppConfig
publicclassAppConfig{
publicDiscountPolicydiscountPolicy(){
// return new FixDiscountPolicy();returnnewRateDiscountPolicy();
}
}
DIP, OCP가 잘 지켜지고 있다.
전체 흐름 정리
새로운 할인 정책 개발
다형성 덕분에 새로운 정률 할인 정책 코드를 추가로 개발하는 것 자체는 아무 문제가 없음
새로운 할인 정책 적용과 문제점
새로 개발한 정률 할인 정책을 적용하려고 하니 '클라이언트 코드'인 주문서비스 구현체도 함께 변경해야 함.
주문 서비스 클라이언트가 인터페이스인 DiscountPolicy뿐만 아니라 구체 클래스인 FixDiscountPolicy도 함께 의존 -> DIP 위반
관심사의 분리
애플리케이션을 하나의 공연으로 생각
기존에는 클라이언트가 의존하는 서버 구현 객체를 직접 생성하고 실행함
비유를 하면 기존에는 남자 주인공 배우가 공연도 하고 동시에 여자 주인공도 직접 초빙하는 다양한 책임을 가지고 있음
공연을 구성하고 담당배우를 섭외하고 지정하는 책임을 담당하는 별도의 '공연기획자'가 나올 시점
공연 기획자인 AppConfig rk emdwkd
AppConfig는 애플리케이션의 전체 동작 방식을 구성하기 위해 '구현 객체를 생성'하고 '연결'하는 책임
이제부터 클라이언트 객체는 자신의 역할을 실행하는 것만 집중, 권한이 줄어든다.(책임이 명확해짐)
AppConfig 리팩터링
구성 정보에서 역할과 구현을 명확하게 분리
역할이 잘 들어남
중복 제거
새로운 구조의 할인 정책 적용
정액 할인 정책 >> 정률 할인 정책으로 변경
AppConfig의 등장으로 애플리케이션이 크게 사용영역과 객체를 생성하고 구성하는 영역으로 분리
할인 정책을 변경해도 AppConfig가 있는 구성 영역만 변경하면 된다. 사용 영역은 변경할 필요가 없고 물론 클라이언트 코드인 주문 서비스 코드도 변경하지 않는다.
좋은 객체 지향 설계의 5가지 원칙의 적용
여기서는 3가지 SRP, DIP, OCP 적용
SRP 단일 책임 원칙
"한 클래스는 하나의 책임만 가져야 한다."
클라이언트 객체는 직접 구현 객체를 생성하고 연결하고 실행하는 다양한 책임을 가지고 있다.
SRP 단일 책임 원칙을 따르면서 관심사를 분리한다.
구현 객체를 생성하고 연결하는 책임은 AppConfig가 담당한다.
클라이언트 객체는 실행하는 책임만 담당
DIP 의존관계 역전 원칙
"프로그래머는 추상화에 의존해야지 구체화에 의존하면 안된다" 의존성 주입은 이 원칙을 따르는 방법 중 하나다.
새로운 할인 정책을 개발하고 적용하려고 하니 클라이언트 코드도 함께 변경해야 했다. 왜냐하면 기존 클라이언트 코드(OrderServiceImpl) 는 DIP를 지키며 DiscountPolicy추상화 인터페이스에 의존하는 것 같았지만, FixDiscountPolicy 구체화 구현 클래스에도 함께 의존했다.
클라이언트 코드가 DiscountPolicy 추상화 인터페이스에만 의존하도록 코드를 변경했다.
하지만 클라이언트 코드는 인터페이스만으로는 아무것도 실행할 수 없다. >>NullPointException
AppConfig 가 FixDiscountPolicy 객체 인스턴스를 클라이언트 코드 대신 생성해서 클라이언트 코드에 의존관계를 주입했다. 이렇게 해서 DIP 원칙을 따르면서 문제도 해결했다.
OCP
"소프트웨어 요소는 확장에는 열려 있으나 변경에는 닫혀 있어야 한다"
다형성 사용하고 클라이언트가 DIP를 지킨다.
애플리케이션을 사용 영역과 구성 영역으로 나눈다.
AppConfig 가 의존관계를 'FixDisountPolicy' > 'RateDiscountPolicy' 변경해서 클라이언트 코드에 주입하므로 클라이언트 코드는 변경하지 않아도된다.
"소프트웨어 요소를 새롭게 확장해도 사용 영역의 변경은 닫혀 있다."
IoC, DI 그리고 컨테이너
제어의 역전 IoC (Inversion of Control)
클라이언트가 내 코드를 대신 호출
기존 프로그램은 클라이언트 구현 객체가 스스로 필요한 서버 구현 객체를 생성하고 연결하고 실행했. 한마디로 구현 객체가 프로그램의 제어 흐름을 스스로 조종했다. 개발자 입장에서는 자연스러운 흐름이다.
반면에 AppConfig 가 등장한 이후에 구현 객체는 자신의 로직을 실행하는 역할만 담당한다. 프로그램 제어 흐름은 이제 AppConfig 가 가져간다. 예를 들어서 OrderServiceImpl 은 필요한 인터페이스를 호출하지만 어떤 구현 객체들이 실행될 지 모른다.
프로그램에 대한 제어 흐름에 대한 권한은 모두 AppConfig가 가지고 있다. 심지어 OrderServiceImpl도 AppConfig가 생성한다. 그리고 AppConfig는 OrderSerrviceImpl이 아닌 OrderService 인터페이스의 다른 구현 객체를 생성하고 실행할 수도 잇다. 그런 사실도 모른채 OrderServiceImpl 은 묵묵히 자신의 로직을 실행할 뿐이다.
이렇듯 프로그램의 제어 흐름을 직접 제어하는 것이 아니라 외부에서 관리하는 것을 제어의 역전이라고 한다.
프레임 워크 vs 라이브러리
프레임 워크가 내가 작성한 코드를 제어하고 대신 실행하면 그것은 프레임워크가 맞다(JUnit)
반면에 내가 작성한 코드가 직접 제어의 흐름을 담당한다면 그것은 프레임워크가 아닌 라이브러리다.
의존 관계 주입 DI, (Dependency Injection)
OrderServiceImpl은 DiscountPolicy 인터페이스에 의존한다. 실제 어떤 구현 객체가 사용될지는 모른다.
의존 관계는 "정적인 클래스 의존 관계와, 실행 시점에 결정되는 동적인 객체(인스턴스) 의존관계" 들을 분리해서 생각해야 한다.
정적인 클래스 의존관계
import만 봐도 의존관계를 쉽게 판단할 수 있다.
정적인 의존 관계는 애플리케이션을 실행하지 않아도 분석할수 있다. OrderServiceImpl은 MemberRepository, DiscountPolicy에 의존한다는 것을 알 수 있다.
그런데 이러한 클래스 의존관계만으로는 실제 어떤 객체가 OrdeServiceImpl에 주입될 수 없다.
클래스 다이어그램
'화살표 가리키면 의존관계'
**의존관계 주입을 하면 정적 클래스 의존관계(다이어그램)(애플리케이션)변경하지 않고 동적인 객체 인스턴스 의존관계를 쉽게 변경할 수 있다.**
IoC 컨테이너, DI 컨테이너
AppConfig 처럼 객체를 생성하고 관리하면서 의존관계를 연결해주는 것을 IoC컨테이너 또는 DI 컨테이너 라 한다.
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
새로운 할인 정책 개발
상황(입장)
RataDiscountPolicy 추가
코드
JUnit5
새로운 할인 정책 적용과 문제점
현재, 할인 정책을 변경하려면 클라이언트인
OrderServiceImpl코드를 고쳐야 한다.문제점 발견
우리는 역할과 구현을 충실히 분리했다. (O)
다형성도 활용하고 인터페이스와 구현 객체를 분리했다. (O)
OCP, DIP 같은 객체 지향 설계 원칙을 충실히 준수했다. (X)
DIP : 클래스 의존관계를 분석해보면 추상(인터페이스)뿐만 아니라 '구체(구현) 클래스에서도 의존하고 있다.
DiscountPolicyFixDiscountPolicy,RateDiscountPolicyOCP : 변경하지 않고 확장할 수 있다고 했는데
왜 클라이언트 코드를 분석해야 할까 ?
정책 변경
어떻게 문제를 해결할 수 있을까 ?
OrderServiceImpl은DiscountPolicy의 인터페이스 뿐만 아니라 구체 클래스도 함께 의존한다.해결 방안
누군가 클라이언트인
OrderServiceImpl에DiscountPolicy의 구현 객체를 대신 생성하고 주입해주어야 한다.관심사의 분리
인터페이스를 배역이라고 생각하자.
그런데 실제 배역에 맞는 배우를 선택하는 것은 누가 하는가 ?
-> 공연 기획자가 한다.
AppConfig의 등장
구현 객체를 생성한다.MemberServiceImplMemoroyMemberRepositoryOrderServiceImplFixDiscountPolicyMemberServiceImpl->MemoryMemberRepositoryOrderServiceImpl ->MemoryMemberRepository,FixDiscountPolicy`DI
MemberServiceImpl은MemoryMemberRepository를 의존하지 않는다.MemberRepository인터페이스만 의존한다.MemberServiceImpl입장에서 생성자를 통해 어떤 구현 객체가 들어올지 알 수 없다.MemberServiceImpl의 생성자를 통해서 어떤 구현 객체를 주입할지는 오직 외부AppConfig에서 결정된다.MemberServiceImpl은 이제부터 '의존관계에 대한 고민은 외부'에 맡기고 '실행헤만 집중하면 된다.'그림
AppConfig객체는MemberRepository객체를 생성하고 그 참조값을MemberSeviceImpl을 생성하면서 생성자로 전달한다.MemberServiceImpl입자에서 보면 의존관계를 마치 외부에서 주입해주는 것 같다고 해서 DI(Dependency Injection), 의존성 주입, 의존관계 주입이라고 한다.Test
정리
AppConfig를 통해서 관심사를 확실하게 분리했다.AppConfig는 공연 기획자다.AppConfig는 구체클래스를 선택한다. 배역에 맞는 담당 배우를 선택한다. 애플리케이션이 어떻게 동작해야 할지 전체 구성을 책임진다.OrderServiceImpl은 기능을 실행하는 책임만 지면 된다.AppConfig 리팩토링
기대하는 그림
역할을 드러나게 하는 것이 중요
중복
MemoryMemberRepository()Extra Method로 중복값을 메소드명을 바꿔 뽑아줬다.
세가지의 역할이 드러난다.
역할과 구현이 한눈에 들어오게 표현이 된다.
MemoryMemberRepository를 다른 구현체로 변경할때 한 부분만 변경하면 된다.AppConfig를 보면 역할과 구현 클래스가 한눈에 들어온다.새로운 구조와 할인 정책 적용
FixDiscountPolicy -> RateDiscountPolicy
AppConfig의 등장으로 애플리케이션이 크게 사용 영역과 객체를 구성하는 영역으로 분리된다.
할인 정책을 변경한다면 어디가 영향을 받을까 ?
: AppConfig
전체 흐름 정리
새로운 할인 정책 개발
새로운 할인 정책 적용과 문제점
새로 개발한 정률 할인 정책을 적용하려고 하니 '클라이언트 코드'인 주문서비스 구현체도 함께 변경해야 함.
주문 서비스 클라이언트가 인터페이스인
DiscountPolicy뿐만 아니라 구체 클래스인FixDiscountPolicy도 함께 의존 -> DIP 위반관심사의 분리
AppConfig 리팩터링
새로운 구조의 할인 정책 적용
좋은 객체 지향 설계의 5가지 원칙의 적용
SRP 단일 책임 원칙
"한 클래스는 하나의 책임만 가져야 한다."
DIP 의존관계 역전 원칙
"프로그래머는 추상화에 의존해야지 구체화에 의존하면 안된다" 의존성 주입은 이 원칙을 따르는 방법 중 하나다.
OrderServiceImpl) 는 DIP를 지키며DiscountPolicy추상화 인터페이스에 의존하는 것 같았지만,FixDiscountPolicy구체화 구현 클래스에도 함께 의존했다.DiscountPolicy추상화 인터페이스에만 의존하도록 코드를 변경했다.FixDiscountPolicy객체 인스턴스를 클라이언트 코드 대신 생성해서 클라이언트 코드에 의존관계를 주입했다. 이렇게 해서 DIP 원칙을 따르면서 문제도 해결했다.OCP
"소프트웨어 요소는 확장에는 열려 있으나 변경에는 닫혀 있어야 한다"
IoC, DI 그리고 컨테이너
제어의 역전 IoC (Inversion of Control)
프레임 워크 vs 라이브러리
의존 관계 주입 DI, (Dependency Injection)
OrderServiceImpl은 DiscountPolicy 인터페이스에 의존한다. 실제 어떤 구현 객체가 사용될지는 모른다.정적인 클래스 의존관계
import만 봐도 의존관계를 쉽게 판단할 수 있다.
정적인 의존 관계는 애플리케이션을 실행하지 않아도 분석할수 있다.
OrderServiceImpl은MemberRepository,DiscountPolicy에 의존한다는 것을 알 수 있다.그런데 이러한 클래스 의존관계만으로는 실제 어떤 객체가
OrdeServiceImpl에 주입될 수 없다.클래스 다이어그램

'화살표 가리키면 의존관계'
IoC 컨테이너, DI 컨테이너
스프링으로 전환하기
AppConfig 각 메서드 앞에 @bean 이라고 적기
MemberApp
logback.xml 만들기
MemberApp 돌리기

강의 메뉴얼과 스프링부트 깃허브 위키 릴리즈 노트에도 나와있지만 로그 레벨이 부트3.1부터 INFO로 변경되어 발생한 현상입니다 😀
OrderApp
스프링 컨테이너
단축키
Ctrl + Shift + T: Create New TestCtrl + E: Recent File (최근 파일)Ctrl+Alt+M: Extra/IntroduceCtrl + N: NavigateAll reactions