일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 |
- JPA
- 정처기
- 캐시
- 정보처리기사
- Spring Boot
- 레디스
- 영속성 컨텍스트
- NoSQL
- 동적계획법
- Redis
- 실행 컨텍스트
- 가상 면접 사례로 배우는 대규모 시스템 설계 기초
- 스프링 부트
- 이벤트루프
- in-memory
- 다이나믹프로그래밍
- sqld
- SQL
- 깃허브
- github
- 분할정복
- 자바의 정석
- spring security
- document database
- 게시판
- VMware
- MongoDB
- 스프링 시큐리티
- 스프링부트
- 호이스팅
- Today
- Total
FreeHand
[스프링 Core] 스프링 핵심 원리 - 기본편1 본문
인프런 김영한님 [스프링 핵심 원리 - 기본편] 강의 정리
순수 자바로 작성
회원 도메인
회원 도메인은 저장소가 확실히 결정되지 않은 상태이다.
따라서 MemberRepository라는 인터페이스를 의존하는 관계로 작성한다.
- Repository
public interface MemberRepository {
void save(Member member);
Member findById(Long memberId);
}
public class MemoryMemberRepository implements MemberRepository{
private static Map<Long, Member> store = new HashMap<>();
@Override
public void save(Member member) {
store.put(member.getId(), member);
}
@Override
public Member findById(Long memberId) {
return store.get(memberId);
}
}
- Service
public interface MemberService {
void join(Member member);
Member findMember(Long memberId);
}
public class MemberServiceImpl implements MemberService{
private final MemberRepository memberRepository = new MemoryMemberRepository();
@Override
public void join(Member member) {
memberRepository.save(member);
}
@Override
public Member findMember(Long memberId) {
return memberRepository.findById(memberId);
}
}
할인과 주문 도메인
할인 역시 정확한 할인 정책이 정해지지 않은 상황이다.
따라서 주문 서비스는 인터페이스인 DiscountPolicy를 의존하도록 설계한다.
public interface DiscountPolicy {
// @return 할인 대상 금액
int discount(Member member, int price);
}
public class FixDiscountPolicy implements DiscountPolicy{
private int discountFixAmount = 1000; // 1000원 할인
@Override
public int discount(Member member, int price) {
if (member.getGrade() == Grade.VIP) {
return discountFixAmount;
} else {
return 0;
}
}
}
public interface OrderService {
Order createOrder(Long memberId, String itemName, int itemPrice);
}
public class OrderServiceImpl implements OrderService{
private final MemberRepository memberRepository = new MemoryMemberRepository();
private final DiscountPolicy discountPolicy = new FixDiscountPolicy();
@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);
}
}
테스트
public class MemberServiceTest {
MemberService memberService = new MemberServiceImpl();
@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 OrderServiceTest {
MemberService memberService = new MemberServiceImpl();
OrderService orderService = new OrderServiceImpl();
@Test
void createOrder() {
// given
long memberId = 1L;
Member member = new Member(memberId, "memberA", Grade.VIP);
memberService.join(member);
// when
Order order = orderService.createOrder(memberId, "itemA", 10000);
// then
Assertions.assertThat(order.getDiscountPrice()).isEqualTo(1000);
}
}
객체지향 원리 적용
위의 코드를 보면 할인 정책으로 FixDiscountPolicy를 사용하고 있다.
만약 할인 정책을 RateDiscountPolicy로 변경하려면 OrderServiceImpl을 수정해야 한다.
public class OrderServiceImpl implements OrderService{
private final MemberRepository memberRepository = new MemoryMemberRepository();
// new FixDiscountPolicy -> new RateDiscountPolicy로 변경 필요
private final DiscountPolicy discountPolicy = new RateDiscountPolicy();
@Override
public Order createOrder(Long memberId, String itemName, int itemPrice) {
// 생략
}
}
DIP와 OCP를 위반하고 있기 때문에 이러한 코드 수정이 필요한 것이다.
DIP를 지키기 위해 인터페이스를 의존하도록 설계했지만, 코드를 보면 인터페이스와 구현체 모두 의존하고 있다.
private final DiscountPolicy discountPolicy = new FixDiscountPolicy();
따라서 다음과 같은 변경이 발생하고, 결국 할인 정책을 변경하기 위해 OrderServiceImpl의 코드도 수정해야 하는 것이다.
즉 OCP가 지켜지지 않는다.
이런 문제를 해결하기 위해 코드를 다음과 같이 수정한다.
public class MemberServiceImpl implements MemberService{
private final MemberRepository memberRepository;
public MemberServiceImpl(MemberRepository memberRepository) {
this.memberRepository = 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;
}
// 생략
}
두 서비스 클래스에서 구현체는 직접 생성하지 않고 생성자를 통해 주입 받도록 수정했다.
이를 통해 인터페이스만 의존하여 DIP를 지킬 수 있게 되었다.
public class AppConfig {
public MemberRepository memberRepository() {
return new MemoryMemberRepository();
}
public MemberService memberService() {
return new MemberServiceImpl(memberRepository());
}
public DiscountPolicy discountPolicy() {
return new FixDiscountPolicy();
}
public OrderService orderService() {
return new OrderServiceImpl(memberRepository(), discountPolicy());
}
}
구현체를 직접 생성하고 주입하는 역할은 AppConfig라는 클래스에서 하도록 작성한다.
결과적으로 할인 정책을 RateDiscountPolicy로 바꾸려면 OrderServiceImpl은 변경이 없고, AppConfig의 discountPolicy 메서드만 수정하면 된다.
스프링 적용
이제 위 코드에 스프링을 적용한다.
@Configuration
public class AppConfig {
@Bean
public MemberRepository memberRepository() {
return new MemoryMemberRepository();
}
@Bean
public MemberService memberService() {
return new MemberServiceImpl(memberRepository());
}
@Bean
public DiscountPolicy discountPolicy() {
return new FixDiscountPolicy();
}
@Bean
public OrderService orderService() {
return new OrderServiceImpl(memberRepository(), discountPolicy());
}
}
우선 AppConfig 클래스에 @Configuration 어노테이션을 추가하고 메서드에는 @Bean 어노테이션을 추가한다.
public class MemberApp {
public static void main(String[] args) {
// AppConfig appConfig = new AppConfig();
// MemberService memberService = appConfig.memberService();
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
MemberService memberService = applicationContext.getBean("memberService", MemberService.class);
Member member1 = new Member(1L, "member1", Grade.VIP);
memberService.join(member1);
Member findMember = memberService.findMember(1L);
System.out.println("new member = " + member1.getName());
System.out.println("find member = " + findMember.getName());
}
}
public class OrderApp {
public static void main(String[] args) {
// AppConfig appConfig = new AppConfig();
// MemberService memberService = appConfig.memberService();
// OrderService orderService = appConfig.orderService();
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
MemberService memberService = applicationContext.getBean("memberService", MemberService.class);
OrderService orderService = applicationContext.getBean("orderService", OrderService.class);
Member member = new Member(1L, "memberA", Grade.VIP);
memberService.join(member);
Order order = orderService.createOrder(1L, "itemA", 20000);
System.out.println("order = " + order);
}
}
기존에 AppConfig 객체가 생성되는 부분은 이제 필요없다.
대신 ApplicationContext 객체를 생성한다. 이 객체가 스프링 컨테이너라고 생각하면 된다.
@Configuration이 붙은 클래스에서 @Bean이 붙은 모든 메서드를 실행하여 반환되는 객체를 스프링 컨테이너에 Bean 객체로 등록한다. 이때 특별히 명시하지 않으면 빈 객체의 이름은 메서드 이름과 같다.
따라서 getBean("빈객체명", 타입.class)로 해당 빈 객체를 호출할 수 있다.
'Web > Spring' 카테고리의 다른 글
[Spring Boot] 게시판 프로젝트 - 05. 댓글 기능 (0) | 2023.10.04 |
---|---|
[Spring Boot] 게시판 프로젝트 - 04. 회원정보 수정 (0) | 2023.10.01 |
[Spring Boot] 게시판 프로젝트 - 03. 게시판 글 CRUD (0) | 2023.09.30 |
[Spring Boot] 게시판 프로젝트 - 02. 회원가입 및 로그인 (0) | 2023.09.25 |
[Spring Boot] 게시판 프로젝트 - 01. 개발환경 및 엔티티 작성 (0) | 2023.09.25 |