public class AppConfig {
// AppConfig라는 클래스를 정의하여 애플리케이션 서비스를 구성합니다.
public MemberService memberService() {
// MemberService 타입의 객체를 생성하고 반환하는 메서드입니다.
return new MemberServiceImpl(new MemoryMemberRepository());
// MemberServiceImpl 객체를 생성하며, 이 때 MemoryMemberRepository 객체를 생성자를 통해 주입합니다.
// 이는 의존성 주입의 한 예로, MemberServiceImpl이 MemoryMemberRepository에 의존하지만, 직접 생성하지 않습니다.
}
public OrderService orderService() {
// OrderService 타입의 객체를 생성하고 반환하는 메서드입니다.
return new OrderServiceImpl(new MemoryMemberRepository(), new FixDiscountPolicy());
// OrderServiceImpl 객체를 생성하며, 이 때 MemoryMemberRepository와 FixDiscountPolicy 객체를 생성자를 통해 주입합니다.
// 이것도 의존성 주입의 예로, OrderServiceImpl은 MemoryMemberRepository와 FixDiscountPolicy에 의존하지만, 직접 생성하지 않습니다.
}
}
public class MemberServiceImpl implements MemberService {
// MemberService 인터페이스를 구현하는 MemberServiceImpl 클래스를 정의합니다. 이 클래스는 회원 관리 기능을 제공합니다.
private final MemberRepository memberRepository;
// MemberRepository 인터페이스에 대한 참조를 저장하는 private 필드입니다. final 키워드는 변수가 생성자에서 초기화된 이후에는 변경될 수 없음을 의미합니다.
public MemberServiceImpl(MemberRepository memberRepository) {
// MemberServiceImpl 클래스의 생성자입니다. MemberRepository 타입의 객체를 인자로 받아서 클래스 내의 memberRepository 필드를 초기화합니다.
this.memberRepository = memberRepository;
// 인자로 받은 memberRepository 객체를 클래스의 memberRepository 필드에 할당합니다. 이는 생성자를 통한 의존성 주입의 예입니다.
}
@Override
public void join(Member member) {
// Member 인터페이스의 join 메서드를 구현합니다. Member 타입의 객체 member를 인자로 받습니다.
memberRepository.save(member);
// memberRepository의 save 메서드를 호출하여 member 객체를 저장합니다.
}
@Override
public Member findMember(Long memberId) {
// Member 인터페이스의 findMember 메서드를 구현합니다. Long 타입의 memberId를 인자로 받습니다.
return memberRepository.findById(memberId);
// memberRepository의 findById 메서드를 호출하여 memberId에 해당하는 Member 객체를 반환합니다.
}
}
생성자 주입
생성자 주입(Constructor Injection)은 의존성 주입(DI)의 한 방법으로, 객체가 생성될 때 필요한 의존성을 생성자를 통해 제공받는 방식입니다.
이 방식은 객체의 불변성을 보장하고, 의존성이 명시적으로 처리되기 때문에 코드의 명확성과 테스트 용이성을 증가시킵니다. 또한, 모든 의존성이 객체가 완전히 생성되기 전에 존재함을 보장하므로, NullPointerException을 방지할 수 있습니다.
이 클래스에서는 MemberRepository를 생성자를 통해 주입받고 있어, MemberServiceImpl 객체는 MemberRepository 없이는 인스턴스화 될 수 없습니다. 이는 결합도를 낮추고, 각 구성 요소의 테스트 및 유지 관리를 용이하게 합니다.
public class OrderServiceImpl implements OrderService {
// OrderService 인터페이스를 구현하는 OrderServiceImpl 클래스를 정의합니다. 이 클래스는 주문 관리 기능을 제공합니다.
private final MemberRepository memberRepository;
// MemberRepository 인터페이스에 대한 참조를 저장하는 private 필드입니다. final 키워드는 이 변수가 생성자에서만 초기화되며 변경되지 않음을 보장합니다.
private final DiscountPolicy discountPolicy;
// DiscountPolicy 인터페이스에 대한 참조를 저장하는 private 필드입니다. 이 역시 final 키워드로 선언되어 변경할 수 없습니다.
public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
// OrderServiceImpl 클래스의 생성자입니다. MemberRepository와 DiscountPolicy 타입의 객체를 인자로 받아서 클래스 내의 memberRepository와 discountPolicy 필드를 초기화합니다.
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
// 인자로 받은 memberRepository와 discountPolicy 객체를 클래스의 각 필드에 할당합니다. 이는 생성자를 통한 의존성 주입의 예입니다.
}
@Override
public Order createOrder(Long memberId, String itemName, int itemPrice) {
// OrderService 인터페이스의 createOrder 메서드를 구현합니다. memberId, itemName, itemPrice를 인자로 받습니다.
Member member = memberRepository.findById(memberId);
// memberId를 사용하여 memberRepository에서 Member 객체를 조회합니다.
int discountPrice = discountPolicy.discount(member, itemPrice);
// 조회된 Member 객체와 itemPrice를 사용하여 discountPolicy의 discount 메서드를 호출하고, 할인된 가격을 계산합니다.
return new Order(memberId, itemName, itemPrice, discountPrice);
// 주문 정보를 포함하는 새 Order 객체를 생성하고 반환합니다.
}
}
public class MemberApp {
// MemberApp 클래스를 정의합니다. 이 클래스는 어플리케이션의 진입점을 포함합니다.
public static void main(String[] args) {
// main 메서드를 정의합니다. 이 메서드는 프로그램의 시작점입니다.
AppConfig appConfig = new AppConfig();
// AppConfig 클래스의 인스턴스를 생성합니다. AppConfig는 의존성 주입 컨테이너로 작동하여 애플리케이션의 설정 및 구성을 담당합니다.
MemberService memberService = appConfig.memberService();
// AppConfig 인스턴스를 통해 MemberService 타입의 객체를 요청합니다. 이는 의존성 주입을 통해 MemberServiceImpl 객체를 반환받습니다.
Member member = new Member(1L, "member1", Grade.VIP);
// Member 타입의 객체를 생성합니다. 이 객체는 ID가 1L, 이름이 "member1", 등급이 VIP인 회원을 나타냅니다.
memberService.join(member);
// memberService를 사용하여 member 객체를 회원으로 등록합니다. join 메서드는 내부적으로 MemberRepository의 save 메서드를 호출합니다.
Member findMember = memberService.findMember(1L);
// memberService를 사용하여 ID가 1L인 회원을 조회합니다. findMember 메서드는 내부적으로 MemberRepository의 findById 메서드를 호출합니다.
System.out.println("new member = " + member.getName());
// 콘솔에 새로 등록한 회원의 이름을 출력합니다.
System.out.println("find Member = " + findMember.getName());
// 콘솔에 조회한 회원의 이름을 출력합니다.
}
}
AppConfig를 통한 의존성 주입
AppConfig 클래스는 의존성 주입을 구현하기 위한 중앙 관리 역할을 합니다. 이 클래스는 애플리케이션에서 필요한 객체들을 생성하고, 그 객체들 간의 의존 관계를 설정하는 책임을 집니다. 여기서 AppConfig는 MemberService 인터페이스의 구현체인 MemberServiceImpl 인스턴스를 생성할 때 필요한 MemberRepository 객체를 생성자를 통해 MemberServiceImpl에 주입합니다. 이 과정은 아래와 같은 장점을 갖습니다:
- 결합도 감소: 구성 요소 간의 의존성이 AppConfig에 의해 관리되므로, 각 클래스는 자신의 의존 객체를 직접 생성하지 않아도 됩니다. 이로 인해 결합도가 감소하고, 유지보수 및 확장성이 향상됩니다.
- 유연한 구성: 의존성을 외부에서 주입받기 때문에, 구성 변경이 필요할 때 AppConfig만 수정하면 됩니다. 예를 들어, 다른 MemberRepository 구현체를 사용하고 싶을 때 AppConfig의 코드 한 줄만 변경하면 됩니다.
- 테스트 용이성: 의존성을 주입받기 때문에, 테스트 중에는 실제 구현 대신 모의 객체(Mock Object)를 주입하여 테스트를 단순화할 수 있습니다
public class OrderApp {
// OrderApp 클래스를 정의합니다. 이 클래스는 애플리케이션의 진입점을 포함하며 주문 관리 기능을 수행합니다.
public static void main(String[] args) {
// main 메서드를 정의합니다. 이 메서드는 프로그램의 시작점입니다.
AppConfig appConfig = new AppConfig();
// AppConfig 클래스의 인스턴스를 생성합니다. AppConfig는 의존성 주입 컨테이너로 작동하여 애플리케이션의 설정 및 구성을 담당합니다.
MemberService memberService = appConfig.memberService();
// AppConfig 인스턴스를 통해 MemberService 타입의 객체를 요청합니다. 이는 의존성 주입을 통해 MemberServiceImpl 객체를 반환받습니다.
OrderService orderService = appConfig.orderService();
// AppConfig 인스턴스를 통해 OrderService 타입의 객체를 요청합니다. 이는 의존성 주입을 통해 OrderServiceImpl 객체를 반환받습니다.
Long memberId = 1L;
// 회원 식별자를 위한 Long 타입의 변수 memberId를 선언하고 1L로 초기화합니다.
Member member = new Member(memberId, "member1", Grade.VIP);
// Member 타입의 객체를 생성합니다. 이 객체는 ID가 memberId, 이름이 "member1", 등급이 VIP인 회원을 나타냅니다.
memberService.join(member);
// memberService를 사용하여 member 객체를 회원으로 등록합니다. join 메서드는 내부적으로 MemberRepository의 save 메서드를 호출합니다.
Order order = orderService.createOrder(memberId, "item1", 10000);
// orderService를 사용하여 주문을 생성합니다. 주문은 memberId, 상품명 "item1", 가격 10000원으로 설정됩니다.
System.out.println("order =" + order);
// 콘솔에 생성된 주문 객체를 출력합니다. Order 클래스의 toString 메서드에 의해 형식이 정의됩니다.
}
}
AppConfig 클래스는 의존성 주입을 구현하기 위한 중앙 관리 역할을 합니다. 이 클래스는 애플리케이션에서 필요한 서비스 객체들을 생성하고, 그 객체들 간의 의존 관계를 설정하는 책임을 집니다. 여기서 AppConfig는 MemberService와 OrderService 인터페이스의 구현체인 MemberServiceImpl과 OrderServiceImpl 인스턴스를 생성할 때 필요한 MemberRepository, DiscountPolicy 객체를 생성자를 통해 각 서비스에 주입합니다.
public class MemberServiceTest {
// MemberServiceTest 클래스를 정의합니다. 이 클래스는 MemberService의 기능을 테스트하기 위한 테스트 케이스를 포함합니다.
MemberService memberService;
// MemberService 인터페이스를 구현하는 객체의 참조를 저장할 필드입니다.
@BeforeEach
public void beforeEach() {
// 각 테스트 메서드가 실행되기 전에 실행될 메서드를 정의합니다. 이 메서드는 테스트 환경을 초기화하는 데 사용됩니다.
AppConfig appConfig = new AppConfig();
// AppConfig 클래스의 인스턴스를 생성합니다. AppConfig는 의존성 주입 컨테이너로 작동하여 테스트에 필요한 서비스 구성을 제공합니다.
memberService = appConfig.memberService();
// AppConfig 인스턴스를 통해 MemberService 타입의 객체를 요청하고, 반환된 객체를 memberService 필드에 할당합니다.
}
@Test
void join() {
// 테스트 메서드를 정의합니다. 'join' 기능의 정상 작동을 검증합니다.
//given
Member member = new Member(1L, "member1", Grade.VIP);
// 테스트에 사용될 Member 객체를 생성합니다. 이 객체는 ID가 1L, 이름이 "member1", 등급이 VIP인 회원을 나타냅니다.
//when
memberService.join(member);
// memberService의 join 메서드를 호출하여 member 객체를 등록합니다.
Member findMember = memberService.findMember(1L);
// memberService의 findMember 메서드를 호출하여 ID가 1L인 회원을 조회합니다.
//then
Assertions.assertThat(member).isEqualTo(findMember);
// Assertions 클래스의 assertThat 메서드를 사용하여, 등록된 회원과 조회된 회원이 동일한지 검증합니다.
}
}
public class OrderServiceTest {
// OrderServiceTest 클래스를 정의합니다. 이 클래스는 OrderService의 기능을 테스트하기 위한 테스트 케이스를 포함합니다.
MemberService memberService;
// MemberService 인터페이스를 구현하는 객체의 참조를 저장할 필드입니다.
OrderService orderService;
// OrderService 인터페이스를 구현하는 객체의 참조를 저장할 필드입니다.
@BeforeEach
public void beforeEach() {
// 각 테스트 메서드가 실행되기 전에 실행될 메서드를 정의합니다. 이 메서드는 테스트 환경을 초기화하는 데 사용됩니다.
AppConfig appConfig = new AppConfig();
// AppConfig 클래스의 인스턴스를 생성합니다. AppConfig는 의존성 주입 컨테이너로 작동하여 테스트에 필요한 서비스 구성을 제공합니다.
memberService = appConfig.memberService();
// AppConfig 인스턴스를 통해 MemberService 타입의 객체를 요청하고, 반환된 객체를 memberService 필드에 할당합니다.
orderService = appConfig.orderService();
// AppConfig 인스턴스를 통해 OrderService 타입의 객체를 요청하고, 반환된 객체를 orderService 필드에 할당합니다.
}
@Test
void createOrder() {
// 테스트 메서드를 정의합니다. 'createOrder' 기능의 정상 작동을 검증합니다.
Long memberId = 1L;
// 회원 식별자를 위한 Long 타입의 변수 memberId를 선언하고 1L로 초기화합니다.
Member member = new Member(memberId, "member1", Grade.VIP);
// Member 타입의 객체를 생성합니다. 이 객체는 ID가 memberId, 이름이 "member1", 등급이 VIP인 회원을 나타냅니다.
memberService.join(member);
// memberService의 join 메서드를 호출하여 member 객체를 회원으로 등록합니다.
Order order = orderService.createOrder(memberId, "item1", 10000);
// orderService를 사용하여 주문을 생성합니다. 주문은 memberId, 상품명 "item1", 가격 10000원으로 설정됩니다.
Assertions.assertThat(order.getDiscountPrice()).isEqualTo(1000);
// Assertions 클래스의 assertThat 메서드를 사용하여, 주문에서 적용된 할인 금액이 1000원인지 검증합니다.
}
}
BeforeEach 어노테이션은 JUnit 테스트에서 각 테스트가 실행되기 전에 특정 작업을 수행하도록 지정합니다.
이 경우, BeforeEach 메서드는 AppConfig 인스턴스를 생성하여 의존성 주입을 통해 MemberService 객체를 초기화합니다. AppConfig는 애플리케이션의 구성과 서비스의 의존성 관리를 담당하는 클래스로, 테스트 환경에서도 이를 통해 의존성을 주입받습니다. 이 접근 방식은 테스트 코드에서 의존성의 소스를 명확히 하여, 테스트의 독립성과 일관성을 보장합니다. 서비스의 구현이 변경되어도 테스트 코드는 AppConfig를 통해 적절히 조정된 서비스 구현체를 계속 사용할 수 있습니다.