앞에서 만든 OrderServiceImpl 구현체를 보면 다음과 같이 1000원을 할인해주는 FixDiscountPolicy 적용해서
사용하고 있습니다.
package hello.core.order;
public class OrderServiceImpl implements OrderService{
private final DiscountPolicy discountPolicy = new FixDiscountPolicy();
...
}
만약 금액에 따른 할인을 하는 RateDiscountPolicy 를 적용하게 된다면 아래와 같이 OrderServiceImpl 을 수정해야합니다.
package hello.core.order;
public class OrderServiceImpl implements OrderService{
// private final DiscountPolicy discountPolicy = new FixDiscountPolicy();
private DiscountPolicy discountPolicy = new RateDiscountPolicy();
...
}
이는 OrderServiceImpl 이 구현 클래스인 FixDiscountPolicy 와 RateDiscountPolicy 에 의존하고 있습니다.
이 코드는 새로운 기능을 확장해서 변경하게 되면 클라이언트 코드에 영향을 줘서 OCP 를 위반합니다.
때문에 구현 클래스가 아닌 인터페이스 (DiscountPolicy) 에만 의존하도록 아래와 같이 코드를 수정해야 합니다.
package hello.core.order;
public class OrderServiceImpl implements OrderService{
private DiscountPolicy discountPolicy;
...
}
이처럼 인터페이스에만 의존하도록 코드를 수정했는데, 이때 코드를 실행하면 DiscountPolicy 의 구현체가 없기 때문에 null pointer exception 이 발생합니다. 이 문제는 DiscountPolicy 의 구현체를 생성하고 주입해주는 새로운 클라이언트를 만들어서 해결할 수 있습니다.
AppConfig
OrderSerivcelmp 주입
위의 DiscountPolicy 의 구현체를 생성하고 주입해주기 위해 AppConfig 라는 설정 클래스를 만듭니다.
package hello.core;
import hello.core.member.MemberService;
import hello.core.member.MemberServiceImpl;
import hello.core.member.MemoryMemberRepository;
import hello.core.order.FixDiscountPolicy;
import hello.core.order.OrderService;
import hello.core.order.OrderServiceImpl;
public class Appconfig {
public OrderService orderService() {
return new OrderServiceImpl(new MemoryMemberRepository(), new FixDiscountPolicy());
}
}
AppConfig 에서 OrderServiceImpl 의 MemberRepository 와 DiscountPolicy 에 필요한 구현체를 생성합니다.
- MemberRepository -> new MemoryMemberRepository()
- DIscountPolicy -> new FixDiscountPolicy()
AppConfig 를 통해 생성한 객체 인스턴트를 OrderServiceImpl 에서 생성자를 통해 주입(연결) 합니다.
package hello.core.order;
import hello.core.member.Member;
import hello.core.member.MemberRepository;
import hello.core.member.MemoryMemberRepository;
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) {
// TODO Auto-generated method stub
Member member = memberRepository.findById(memberId);
int discountPrice = discountPolicy.discount(member, itemPrice);
return new Order(memberId, itemName, itemPrice, discountPrice);
}
}
이렇게 코드를 수정하면 OrderServiceImpl 은 구현체인 FixDiscountPolicy 를 의존하지 않습니다.
때문에 OrderServiceImpl 입장에서는 생성자를 통해 어떤 구현체가 들어올지 알 수 없고,
AppConfig 에서 구현체를 FixDiscountPolicy 와 RateDiscountPolicy 두가지 중에 결정을 합니다.
MemberServiceImpl 주입
MemberServiceImpl 또한 MemoryMemberRepository() 구현체에 의존하고 있습니다.
package hello.core.member;
public class MemberServiceImpl implements MemberService {
private final MemberRepository memberRepository = new MemoryMemberRepository();
...
}
위와 동일하게 MemberServicelmpl 도 Appconfig 에서 구현체를 연결해주도록 수정을 해보겠습니다.
먼저 MemberServiceImpl 에 생성자를 통해 memverRepository 를 생성합니다.
package hello.core.member;
public class MemberServiceImpl implements MemberService {
private final MemberRepository memberRepository;
public MemberServiceImpl(MemberRepository memberRepository) {
// TODO Auto-generated constructor stub
this.memberRepository = memberRepository;
}
@Override
public void join(Member member) {
// TODO Auto-generated method stub
memberRepository.save(member);
}
@Override
public Member findMember(Long memberId) {
// TODO Auto-generated method stub
return memberRepository.findById(memberId);
}
}
다음으로 Appconfig 에서 MemberService 를 통해 MemoryMeberRepositry 를 생성해서 MemberS
package hello.core;
import hello.core.member.MemberService;
import hello.core.member.MemberServiceImpl;
import hello.core.member.MemoryMemberRepository;
import hello.core.order.FixDiscountPolicy;
import hello.core.order.OrderService;
import hello.core.order.OrderServiceImpl;
public class Appconfig {
public OrderService orderService() {
return new OrderServiceImpl(new MemoryMemberRepository(), new FixDiscountPolicy());
}
public MemberService memberService() {
return new MemberServiceImpl(new MemoryMemberRepository());
}
}
현재까지 결과를 다이어그램으로 나타내면 아래와 같습니다.
OrderServiceImpl 과 MemberServiceImpl 은 구현체 주입은 외부에 맡기고 실행만 하면 됩니다.
Appconfig 리팩터링
현재 Appconfig 는 new MemoryMemberRepository() 가 중복되어 사용되고 있습니다.
만약 MemoryMeberRepositry 를 다른 구현체로 변경하게 될 경우 중복되는 부분을 모두 변경해야 합니다.
때문에 중복되는 부분을 제거하고 역할에 따른 구현이 보이도록 리팩터링 하겠습니다.
package hello.core;
import hello.core.member.MemberRepository;
import hello.core.member.MemberService;
import hello.core.member.MemberServiceImpl;
import hello.core.member.MemoryMemberRepository;
import hello.core.order.DiscountPolicy;
import hello.core.order.FixDiscountPolicy;
import hello.core.order.OrderService;
import hello.core.order.OrderServiceImpl;
public class Appconfig {
public MemberRepository memberRepository() {
return new MemoryMemberRepository();
}
public DiscountPolicy discountPolicy() {
return new FixDiscountPolicy();
}
public OrderService orderService() {
return new OrderServiceImpl(memberRepository(), discountPolicy());
}
public MemberService memberService() {
return new MemberServiceImpl(memberRepository());
}
}
결과
AppConfig 를 통해 애플리케이션을 사용 영역과 객체를 생성하고 구성(Configuration) 하는 영역으로 분리
만약 FixDiscountPolicy 에서 RateDiscountPolicy 로 바뀌어도 사용영역은 수정할 필요없이
AppConfig 의 discountPolicy() 안의 내용만 바꿔주면 됩니다.
package hello.core;
import hello.core.member.MemberRepository;
import hello.core.member.MemberService;
import hello.core.member.MemberServiceImpl;
import hello.core.member.MemoryMemberRepository;
import hello.core.order.DiscountPolicy;
import hello.core.order.FixDiscountPolicy;
import hello.core.order.OrderService;
import hello.core.order.OrderServiceImpl;
import hello.core.order.RateDiscountPolicy;
public class Appconfig {
public DiscountPolicy discountPolicy() {
// return new FixDiscountPolicy();
return new RateDiscountPolicy();
}
...
}
'Spring > Spring 핵심 원리' 카테고리의 다른 글
컴포넌트 스캔 (0) | 2023.01.21 |
---|---|
스프링 컨테이너와 스프링 빈 (0) | 2023.01.20 |
예제 만들기 - 주문과 할인 도메인 설계 (0) | 2023.01.20 |
객체 지향과 스프링 - 좋은 객체 지향 설계의 5가지 원칙 (0) | 2023.01.16 |
객체 지향과 스프링 - 좋은 객체 지향 프로그램이란 (0) | 2023.01.16 |