목차
1. 요구사항
2. 순수 자바 코드로 구현
oop_sample/oop_sample/src at main · kyeonghooon/oop_sample
Contribute to kyeonghooon/oop_sample development by creating an account on GitHub.
github.com
주요 포인트 !!
public class AppConfig {
public MemberRepository getMemberRepository() {
return MemoryMemberRepositoryImpl.getInstance();
}
public MemberService getMemberService() {
return new MemberServiceImpl(getMemberRepository());
}
public DiscountPolicy getDiscountPolicy() {
// 할인 정책 --> 고정 할인
// 변경 --> 정률 할인으로 바꾸면 된다.
// return new FixDiscountPolicyImpl();
return new PercentDiscountPolicyImpl();
}
public OrderService getOrderService() {
return new OrderServiceImpl(getMemberRepository(), getDiscountPolicy());
}
}
인터페이스를 활용하여 정책이 바뀜에 따라 구현체만 바꿔서 사용할 수 있다
만약 새로운 정책이 생긴다고 하더라도 새로운 구현체를 만들고 해당 구현체를 return 하도록 수정 하면 끝!
3. 스프링의 빈 등록 활용 (수동 빈 등록)
oop_sample/oop_sample_spring/oop at main · kyeonghooon/oop_sample
Contribute to kyeonghooon/oop_sample development by creating an account on GitHub.
github.com
package com.example.oop.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.example.oop._domain.member.repository.MemberRepository;
import com.example.oop._domain.member.repository.MemoryMemberRepositoryImpl;
import com.example.oop._domain.member.service.MemberService;
import com.example.oop._domain.member.service.MemberServiceImpl;
import com.example.oop._domain.order.DiscountPolicy;
import com.example.oop._domain.order.FixDiscountPolicyImpl;
import com.example.oop._domain.order.service.OrderService;
import com.example.oop._domain.order.service.OrderServiceImpl;
@Configuration
public class AppConfig {
@Bean
public MemberRepository memberRepository() {
return new MemoryMemberRepositoryImpl();
}
@Bean
public MemberService memberService() {
return new MemberServiceImpl(memberRepository());
}
@Bean
public DiscountPolicy discountPolicy() {
// 할인 정책 설정 (정률 할인 사용)
// return new PercentDiscountPolicyImpl();
// 변경하려면 여기서 다른 구현체로 변경
return new FixDiscountPolicyImpl();
}
@Bean
public OrderService orderService() {
return new OrderServiceImpl(memberRepository(), discountPolicy());
}
}
package com.example.oop._domain.order.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import com.example.oop._domain.member.Grade;
import com.example.oop._domain.member.Member;
import com.example.oop._domain.member.service.MemberService;
import com.example.oop._domain.order.Order;
import com.example.oop._domain.order.service.OrderService;
import lombok.RequiredArgsConstructor;
@RestController
@RequiredArgsConstructor
public class TestController {
private final OrderService orderService;
private final MemberService memberService;
@GetMapping("/test2")
public String test() {
System.out.println("ssss");
Member member = new Member(200L, "홍길동", Grade.VIP);
memberService.signUp(member);
Order order = orderService.createOrder(member.getId(), "아이폰16", 3_000);
return order.toString();
}
}
package com.example.oop._domain.order.service;
import org.springframework.stereotype.Service;
import com.example.oop._domain.member.Member;
import com.example.oop._domain.member.repository.MemberRepository;
import com.example.oop._domain.order.DiscountPolicy;
import com.example.oop._domain.order.Order;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
@Service
public class OrderServiceImpl implements OrderService {
private final MemberRepository memberRepository;
private final 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);
}
}
4. 스프링의 빈 등록 활용 (자동 빈 등록)
일부 예시
package com.example.oop._domain.member.repository;
import java.util.HashMap;
import java.util.Map;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Repository;
import com.example.oop._domain.member.Grade;
import com.example.oop._domain.member.Member;
@Repository
@Profile("dev")
public class MemoryMemberRepositoryImpl implements MemberRepository {
private Map<Long, Member> memberStore = new HashMap<>();
public MemoryMemberRepositoryImpl() {
initData();
}//
// 초기 샘플 데이터
private void initData() {
memberStore.put(1L, new Member(1L, "홍길동", Grade.VIP));
memberStore.put(2L, new Member(2L, "이몽룡", Grade.VIP));
memberStore.put(3L, new Member(3L, "성춘향", Grade.VIP));
}
@Override
public void save(Member member) {
memberStore.put(member.getId(), member);
}
@Override
public Member findById(Long memberId) {
return memberStore.get(memberId);
}
}
- application-dev.yml 을 사용할 때 적용
package com.example.oop._domain.order;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Service;
import com.example.oop._domain.member.Grade;
import com.example.oop._domain.member.Member;
@Service
@Primary
public class FixDiscountPolicyImpl implements DiscountPolicy {
private int discountFixAmount = 1500;
@Override
public int discount(Member member, int price) {
// VIP 회원만 할인 금액 적용 된다.
if (member.getGrade() == Grade.VIP){
return discountFixAmount;
}
return 0;
}
}
- Primary 어노테이션이 붙은 빈이 주입됨
다양한 구현체를 사용할 수록 primary 어노테이션의 관리가 중요해질거라 생각해 따로 문서를 작성함
## 빈 관리 및 @Primary 사용 설명
이 프로젝트에서는 여러 구현체가 존재하는 인터페이스에 대해 기본적으로 사용할 구현체를 설정하기 위해 `@Primary` 어노테이션을 사용하고 있습니다.
### DiscountPolicy 인터페이스 구현체
`DiscountPolicy` 인터페이스는 두 가지 구현체가 존재합니다:
1. **PercentDiscountPolicyImpl** (정률 할인 정책)
2. **FixDiscountPolicyImpl** (고정 할인 정책)
현재 프로젝트에서는 **FixDiscountPolicyImpl**이 기본적으로 사용됩니다.
이를 위해 `@Primary` 어노테이션을 **FixDiscountPolicyImpl** 클래스에 적용하였습니다.
5. 수동 등록 vs 자동 등록
1. 수동 빈 등록 (Manual Bean Registration)
장점:
- 명시적 관리: 어떤 빈이 어떤 클래스에서 생성되는지 명확하게 관리할 수 있다. 빈 생성 및 의존성 주입 과정이 모두 @Configuration 클래스에 명시되므로, 전체 흐름을 쉽게 파악할 수 있다.
- 더 큰 유연성: 특정 조건에 따라 빈을 동적으로 생성하거나, 빈의 구성 방식(예: 빈 초기화 로직)을 커스터마이징할 수 있다. 예를 들어, 특정 환경에 맞춰 다른 빈을 제공할 수 있다.
- 빈 생성 로직 제어 가능: 빈의 생성, 초기화, 주입 등을 세밀하게 제어할 수 있으며, 필요 시 생성 시점이나 라이프사이클을 조절할 수 있다.
- 테스트 용이성: 테스트할 때 특정 빈을 주입하거나 교체할 수 있는 유연성이 높다. 테스트에서 원하는 대로 빈을 설정하고 주입할 수 있다.
단점:
- 코드가 복잡해질 수 있음: 모든 빈을 직접 등록해야 하므로, 프로젝트가 커질수록 @Configuration 클래스에 빈 등록 코드가 많아질 수 있고, 관리가 복잡해질 수 있다.
- 자동 설정의 장점 사용 불가: 스프링의 자동 구성 기능과 @ComponentScan의 이점을 충분히 활용할 수 없다. 모든 빈을 수동으로 등록해야 하므로 개발 속도가 느려질 수 있다.
- 변경에 취약: 새로운 빈을 추가하거나 변경해야 할 때마다 빈 설정을 수정해야 하므로, 빈 관리 코드가 자주 변경될 수 있다.
2. 자동 빈 등록 (Automatic Bean Registration)
장점:
- 간단하고 빠름: @Component, @Service, @Repository, @Controller 등의 어노테이션을 붙이기만 하면 빈이 자동으로 등록되므로 설정이 매우 간단하다. 추가적으로 @ComponentScan을 통해 모든 빈을 자동으로 탐색해 등록할 수 있다.
- 스프링의 자동 설정과 통합: 스프링 부트의 자동 설정 기능을 최대한 활용할 수 있다. 즉, 스프링이 제공하는 다양한 기본 설정을 활용하여 개발 속도를 높이고, 빈 설정과 관리에 대한 수고를 덜 수 있다.
- 간결한 코드: 빈을 별도로 등록할 필요가 없어 코드가 간결해지고 관리가 쉬워진다. 특히 작은 프로젝트나 빈의 수가 적을 때는 자동 빈 등록 방식이 매우 유리하다.
단점:
- 명시성이 부족: 어떤 빈이 등록되고 어디서 사용되는지 자동으로 처리되므로, 대규모 프로젝트에서는 빈의 흐름을 추적하기 어려울 수 있다. 특히 같은 인터페이스를 구현하는 여러 구현체가 있을 때 혼란이 생길 수 있다.
- 유연성 부족: 특정 상황에 따라 빈을 다르게 생성하거나, 빈의 라이프사이클을 제어하기 어려울 수 있다. 빈의 생성 로직을 커스터마이징해야 하는 경우, 자동 빈 등록 방식은 적합하지 않다.
- 복잡한 의존성 관리: 같은 인터페이스의 여러 구현체를 사용할 때, @Primary나 @Qualifier 같은 추가 어노테이션을 사용해 빈을 명시적으로 지정해야 하는 상황이 발생할 수 있다.
3. 정리: 어떤 경우에 어떤 방식을 선택해야 할까?
수동 빈 등록이 적합한 경우:
- 복잡한 빈 관리가 필요하거나, 빈 생성 로직을 세밀하게 제어해야 하는 경우.
- 동적으로 빈을 생성해야 하거나, 환경에 따라 다른 빈을 주입해야 하는 경우.
- 명시적인 빈 관리가 필요하고, 테스트 환경에서 특정 빈을 쉽게 교체하거나 설정해야 할 때.
자동 빈 등록이 적합한 경우:
- 작고 간단한 프로젝트에서, 빈의 수가 많지 않으며 자동으로 생성해도 큰 문제가 없을 때.
- 빠르고 간편한 개발이 필요할 때, 즉 빈 설정에 대한 수고를 덜고, 스프링의 자동 설정 기능을 최대한 활용하고 싶을 때.
- 빈의 생성 로직이 단순하고, 특별한 빈 커스터마이징이 필요하지 않은 경우.
결론:
- 자동 빈 등록은 간단하고 빈의 수가 많지 않으며, 특별한 커스터마이징 없이 자동으로 등록될 때 유리하다.
- 수동 빈 등록은 복잡한 프로젝트에서 빈을 제어하고 관리할 필요가 있을 때 적합히다.
'Spring Boot > 추가 개념' 카테고리의 다른 글
@SessionAttribute 와 HttpSession을 멤버 필드로 주입 받는 것의 문제점 (0) | 2024.10.18 |
---|---|
API 설계 및 모범 사례 (1) | 2024.09.27 |
스프링부트 블로그 만들기 테스트 (0) | 2024.08.19 |
Bank 카카오 소셜로그인 처리 (1) | 2024.08.19 |
OAuth 2.0 이란 (Open Authorization) (0) | 2024.08.19 |