[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를 해주는 것을 알 수 있다. 

BELATED ARTICLES

more