[Spring] AppConfigurer 클래스의 역할
2023. 6. 11. 14:29
Section 3 들어가기 전 한 Section 1에서 실습했던 자바 프로젝트("버거퀸")를 스프링으로 전환하는 실습을 다시 하던 중 AppConfigurer 클래스의 역할과 왜 존재하게 되었는지 정리해 보았다.
✔️ AppConfigurer 를 사용하는 이유?
➡️ 영어 단어 Configuration의 의미는 "환경 설정, 구성" 이다. AppConfig에서의 Config는 이 단어의 줄임말이라고 할 수 있겠다. 즉, App의 환경설정 또는 구성을 나타내는 클래스라고 보면 된다.
➡️ 객체지향적 코드를 짜기 위해서는 SOLID 원칙을 잘 지켜야 한다. 하지만 버거퀸 실습을 할 때 아래와 같은 문제가 있었다.
// 나이가 20 미만인 경우 500원을 할인해 주는 조건
public class KidDiscountCondition {
private FixedAmountDiscountPolicy fixedAmountDiscountPolicy = new FixedAmountDiscountPolicy(500);
...
}
// 특정 학원을 다니는 학생일 경우 10% 할인해 주는 조건
public class CozDiscountCondition {
private FixedRateDiscountPolicy fixedRateDiscountPolicy = new FixedRateDiscountPolicy(10);
...
}
// 주문하기 버튼을 눌렀을 때 호출되는 메서드
public void makeOrder() {
CozDiscountCondition cozDiscountCondition = new CozDiscountCondition();
KidDiscountCondition kidDiscountCondition = new KidDiscountCondition();
...
}
- 이 코드들을 보면 각각의 객체들은 new 연산자를 통해 스스로 자신이 의존할 구현 객체를 선택했고, 어떤 정책에 변화가 있어 코드를 변경해야 할 때 이와 관련된 코드를 일일이 수정해야 하는 번거운 상황이 나타났다.
→ 새로운 할인 정책을 도입할 경우 부수적으로 변경해야 할 코드가 늘어난다.
즉, 개방-폐쇄 원칙(Open-Closed Principle, OCP) 위반
→ 특정 객체가 어떤 객체를 사용할 것인지 직접 결정한다. 역할이 아닌 구현에 의존하고 있다.
즉, 의존성 역전 원칙(Dependency Inversion Principle, DIP) 위반
➡️ 그래서 이 문제를 해결하기 위해 인터페이스를 사용하여 객체의 역할과 구현을 분리시켰다.
public interface DiscountPolicy {
int calculateDiscountedPrice(int price);
}
public class FixedRateDiscountPolicy implements DiscountPolicy { ... }
public class FixedAmountDiscountPolicy implements DiscountPolicy { ... }
public class CozDiscountCondition {
...
private DiscountPolicy discountPolicy = new FixedRateDiscountPolicy(10); // 직접 의존
...
}
- 하지만 이 경우에도 구현 객체는 new 연산자를 사용하여 FixedRateDiscountPolicy에 직접적으로 의존하고 있다. (DIP 위반)
➡️ 이러한 문제들을 해결하기 위해서 사용하는 것이 AppConfigurer 클래스라고 할 수 있다.
public class AppConfigurer {
private Cart cart = new Cart(productRepository(), menu());
public Menu menu() {
return new Menu(productRepository());
}
public ProductRepository productRepository() {
return new ProductRepository();
}
public Cart cart() {
return cart;
}
public Order order() {
return new Order(cart(), discount());
}
public Discount discount() {
return new Discount(new DiscountCondition[] {
new CozDiscountCondition(new FixedRateDiscountPolicy()),
new KidDiscountCondition(new FixedAmountDiscountPolicy())
});
}
}
- 위 코드를 보면 각 역할과 책임에 따라 필요한 객체를 생성한 후에 생성한 객체의 참조값을 생성자를 통해 주입시켜주고 있다.
즉, DI(의존성 주입)를 통해 각 객체들 간 의존 관계를 연결한다고 할 수 있다. - 다시 말하면 더 이상 구현 객체가 코드 실행의 제어권을 가지고 스스로 필요한 객체를 선택하고 생성하여 사용하는 것이 아니라 AppConfigurer 클래스를 통해 사용하게 될 객체가 생성되고 연결되고 있다고 할 수 있다.
→ 여기서 코드 흐름의 주도권이 개발자에게 있는 것이 아니라 프레임워크로 넘어가는 제어의 역전(IoC)이 일어나게 된다.
public class Main {
public static void main(String[] args) {
// AppConfigurer 객체 생성 및 조립
AppConfigurer appConfigurer = new AppConfigurer();
OrderApp orderApp = new OrderApp(
appConfigurer.productRepository(),
appConfigurer.menu(),
appConfigurer.cart(),
appConfigurer.order()
);
orderApp.start();
}
}
public class OrderApp {
// (1) 주입받을 필드 선언
private ProductRepository productRepository;
private Menu menu;
private Cart cart;
private Order order;
// (2) 생성자를 통한 주입
public OrderApp(ProductRepository productRepository, Menu menu, Cart cart, Order order) {
this.productRepository = productRepository;
this.menu = menu;
this.cart = cart;
this.order = order;
}
public void start() {
Scanner scanner = new Scanner(System.in);
System.out.println("🍔 BurgerQueen Order Service");
while (true) {
menu.printMenu();
String input = scanner.nextLine();
if (input.equals("+")) {
order.makeOrder();
break;
}
else {
int menuNumber = Integer.parseInt(input);
if (menuNumber == 0) cart.printCart();
else if (1 <= menuNumber && menuNumber <= productRepository.getAllProducts().length) cart.addToCart(menuNumber);
}
}
}
}
- Main 클래스를 실행하면 AppConfigurer 클래스를 통해 객체를 생성하고 정의한 메서드들을 호출하여 DI를 해주는 것을 알 수 있다.
'Spring > 기본' 카테고리의 다른 글
[Spring] @Configuration, @Component (0) | 2023.06.21 |
---|---|
[Spring] 싱글톤 컨테이너 (1) | 2023.06.11 |
[Spring] 스프링 컨테이너와 빈 (0) | 2023.06.11 |
[Spring] 스프링 프레임워크 주요 모듈 알아보기 (0) | 2023.05.31 |