새로운 할인 정책 개발
서비스 오픈 직전에 할인 정책을 지금처럼 고정 금액이 아니라 좀 더 합리적인 주문 금액당 할인하는 정률 할인으로
변경하려고 함. (%)
public class RateDiscountPolicy implements DiscountPolicy {
private int discountPercent = 10;
@Override
public int discount(Member member, int price) {
if (member.getGrade() == Grade.VIP) {
return price * discountPercent / 100;
} else {
return 0;
}
}
}
새로운 할인정책 구현 클래스 생성
public class OrderServiceImpl implements OrderService {
private final MemberRepository memberRepository = new MemoryMemberRepository();
// private final DiscountPolicy discountPolicy = new FixDiscountPolicy();
private final DiscountPolicy discountPolicy = new RateDiscountPolicy();
@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);
}
}
할인정책 구현체 갈아끼우기
문제점
역할과 구현 분리, 다형성 활용, 인터페이스와 구현 객체 분리 -> OK
OCP, DIP 같은 객체지향 설계 원칙을 충실히 준수하지 않음
DIP 위반 (의존관계의 역전-인터페이스에만 의존)
추상(인터페이스)뿐만 아니라 구체(구현)클래스에도 의존
인터페이스 의존 : DiscountPolicy
구현클래스 : FixDiscountPolicy, RateDiscountPolicy
클라이언트인 OrderServiceImpl이 DiscountPolicy 인터페이스 뿐만 아니라 FixDiscountPolicy 구현체 클래스도
함께 의존하고 있다.
OCP 위반 (개방폐쇄의 원칙-확장을 위해 클라이언트 코드 영향 -> 수정)
코드 기능을 확장해서 변경하면 클라이언트 코드(OrderServiceImpl)에 영향을 준다
해결방법
문제점 정리
OrderServiceImpl은 DiscountPolicy의 인터페이스 뿐 아니라 구체 클래스도 함께 의존
그래서 구체 클래스를 갈아끼울때 OrderServiceImpl도 함께 변경해야함 (DIP 위반)
추상에만 의존하도록 변경이 필요
DIP를 위반하지 않도록 인터페이스에만 의존하도록 의존관계를 변경하면 된다.
public class OrderServiceImpl implements OrderService {
private final MemberRepository memberRepository = new MemoryMemberRepository();
private 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);
}
}
위처럼 DiscountPolicy에만 의존할 수 있도록 코드를 변경
이러면... Null Exception이 뜸 (할당이 안되어 있기 때문에)
따라서 이러한 문제를 해결하기 위해 누군가가 클라이언트인 OrderServiceImpl에 DiscountPolicy의 구현체를 대신
생성해서 할당해주어야 한다!!
관심사의 분리
AppConfig의 등장
애플리케이션의 전체 동작 방식을 구성하기 위해 구현객체를 생성하고 연결하는 책임을 가지는 별도의 설정 클래스를 만든다. (구현클래스를 인터페이스에 조립해주는 역할)
1. 생성자를 통해 할당하는 방법 (생성자 주입)
public class MemberServiceImpl implements MemberService {
private final MemberRepository memberRepository;
// 생성자
public MemberServiceImpl(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
public class AppConfig {
public MemberService memberService() {
// 생성자
return new MemberServiceImpl(new MemoryMemberRepository());
}
}
MemberServiceImpl의 생성자를 통해 MemberRepository를 주입하는데 이걸 AppConfig가 대신 해준다
AppConfig의 역할
- 애플리케이션의 실제 동작에 필요한 구현객체를 생성한다.
- 생성한 객체 인스턴스의 참조(레퍼런스)를 생성자를 통해서 주입(연결,할당) 해준다
이게 엄청 중요한게 memberService에서 생성한 MemoryMemberRepository라는 구현체를
MemberServiceImpl에 참조값을 통해 전달해주는데
이 부분이 "new MemberServiceImpl(new MemoryMemberRepository()) = 생성자" 라는 것
또한, AppConfig는 MemberServiceImpl과 MemoryMemberRespository의 인스턴스 생성 역할 역시 담당한다. - MemberServiceImpl은 MemberRepository 인터페이스만 신경쓰면된다.
MemberRepository의 구현체인 MemoryMemberRepository가 올지 DbMemberRepository가 올지 신경을 안써도된다는 얘기. - AppConfig는 MemoryMemberRepository 객체를 생성하고, 그 참조값을 MemberServiceImpl을 생성하면서
생성자로 전달한다. - MemberServiceImpl 입장에서는 의존관계를 마치 외부에서 주입한다고 해서 DI (Dependency Injection)이라 한다.
실질적으로 실행할때는 AppConfig를 생성함으로써 MemberService에 AppConfig에서 생성한 MemberService를 할당
위에서 보다시피 AppConfig 내부에서는 MemberServiceImpl과 MemoryMemberRepository 구현체를 생성하고 있다.
여기서 생성된 MemberServiceImpl은 MemoryMemberRepository를 사용할것이다라고
명확히 지정해서 전달하고 있다.
public class MemberServiceTest {
MemberService memberService;
@BeforeEach
public void beforeEach() {
AppConfig appConfig = new AppConfig();
memberService = appConfig.memberService();
}
@Test
void join() {
// given
Member member = new Member(1L, "memberA", Grade.VIP);
// when
memberService.join(member);
Member findMember = memberService.findMember(1L);
// then
Assertions.assertThat(member).isEqualTo(findMember);
}
}
테스트코드는 위와 같은 패턴으로 짜는게 좋다
public class AppConfig {
// memberRepository와 discountPolicy는 여러 군데에서 쓸수 있기 때문에 메소드화 하여 사용
private MemoryMemberRepository memberRepository() {
return new MemoryMemberRepository();
}
public DiscountPolicy discountPolicy() {
return new FixDiscountPolicy();
}
public MemberService memberService() {
return new MemberServiceImpl(memberRepository());
}
public OrderService orderService() {
return new OrderServiceImpl(memberRepository(), discountPolicy());
}
}
AppConfig는 위와 같이 리팩토링 해주는 것이 좋다.
AppConfig의 등장으로 애플리케이션은 크게 [사용영역]과
[객체를 생성하고 구성(Configuration)하는 영역]으로 분리되었다.
따라서, 구성이 변경되게 되면 AppConfig만 수정하면된다!
번외 - XML 양식의 AppConfig
GenericXmlApplicationContext ac = new GenericXmlApplicationContext("appConfig.xml");
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="memberService" class="hello.core.member.MemberServiceImpl">
<constructor-arg name="memberRepository" ref="memberRepository"/>
</bean>
<bean id="memberRepository" class="hello.core.member.MemoryMemberRepository"/>
<bean id="orderService" class="hello.core.order.OrderServiceImpl">
<constructor-arg name="memberRepository" ref="memberRepository"/>
<constructor-arg name="discountPolicy" ref="discountPolicy"/>
</bean>
<bean id="discountPolicy" class="hello.core.dicount.RateDiscountPolicy"/>
</beans>
거의 비슷...
'SpringFramework > Spring 중요 개념' 카테고리의 다른 글
[Spring] 스프링으로 변환하기 (0) | 2021.12.15 |
---|---|
[Spring] IOC, DI, 그리고 컨테이너 (0) | 2021.12.15 |
[Spring] 예제 만들기 (순수 Java) (0) | 2021.12.13 |
[Spring] SOLID (0) | 2021.12.13 |
[Spring] 좋은 객체지향 프로그래밍이란? (0) | 2021.12.12 |
댓글