Java/Java

의존성 주입(Dependency Injection, DI)

마손리 2023. 3. 5. 21:50

의존성 주입이란 객체가 자신이 의존할 객체를 스스로 만들도록 하는 것이 아니라 외부에서 주입해주는 것을 의미한다.

 

의존성 주입을 사용하는 이유로는, 어떠한 앱의 하나의 클래스를 수정 혹은 변경이 이뤄질 경우 연결된 다른 클래스들을 수정할 필요 없이 해당 앱의 설정클래스(Appconfigurer)만 변경하게끔 만들기 위해서이다. 즉, 코드의 수정과 변경에 용이하게 끔 만들기위해 의존성 주입을 사용한다.

 

예제)

public class Main {
	// 맨밑의 메인클래스에서 Kiosk 인스턴스를 이용해 앱을 실행하면
	//각각의 인스턴스들이 각자가 필요한 다른 인스턴스들을 직접생성한다.
    static class Kiosk{
        ProductRepository productRepository = new ProductRepository();
        Menu menu = new Menu();
        Cart cart = new Cart();
        Order order = new Order();
    }
    static class ProductRepository{
    }
    static class Cart {
        ProductRepository productRepository = new ProductRepository();
        Menu menu = new Menu();
    }
    static class Order {
        Cart cart = new Cart();
        RateDiscount rateDiscount = new RateDiscount();
        AmountDiscount amountDiscount = new AmountDiscount();
    }
    static class Menu{
        ProductRepository productRepository = new ProductRepository();
    }
    static class RateDiscount{};
    static class AmountDiscount{};
    
    
    public static void main(String[] args) {
        Kiosk kiosk = new Kiosk();
    }
}

위의 코드는 의존성 주입이 전혀 안된 코드이다. 보이는 바와 같이 모든 객체들이 자신이 필요한 다른 객체들을 직접 생성하고 있다. 위의 코드를 의존성 주입을 시켜 구현한다면 아래와 같은 코드처럼 구현할수 있다.

 

public class Main {  
	//생성자를통해 필요한 인스턴스들을 전달받은뒤 지역변수(전역변수)에 할당후 사용
    static class Kiosk{
        ProductRepository productRepository;
        Menu menu;
        Cart cart;
        Order order;

        public Kiosk(ProductRepository productRepository, Menu menu, Cart cart, Order order) {
            this.productRepository = productRepository;
            this.menu = menu;
            this.cart = cart;
            this.order = order;
        }
    }
    static class ProductRepository{
    }
    static class Cart {
        ProductRepository productRepository;
        Menu menu;
        Cart(ProductRepository productRepository, Menu menu){
            this.productRepository = productRepository;
            this.menu = menu;
        }
    }
    static class Menu{
        ProductRepository productRepository;
        Menu(ProductRepository productRepository){
            this.productRepository = productRepository;
        }
    }
    
    //추상화를통해 관련된 클래스들을 묶고 다형성을 이용해 하나의 타입으로 관련
    //클래스들을 생성자메서드를 통해 받고있다.
    static class Order {
        Cart cart;
        Discount[] discounts;
        public Order(Cart cart, Discount[] discounts) {
            this.cart = cart;
            this.discounts = discounts;
        }
    }
	
    //다형성을 이용해 하나의 타입으로 여러 클래스들을 의존성주입해주기 위해서
    //추상화 클래스 Discount를 만들어 관련된 클래스(RateDiscount와 AmountDiscount)들을 묶어주었다.
    static abstract class Discount{}
    static class RateDiscount extends Discount{};
    static class AmountDiscount extends Discount{};
    
    /*↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ 앱 가동에 필요한 객체들 ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑*/
    
    
    // Appconfigurer클래스를 새로 만들어 필요한 모든 인스턴스들을 생성하고
    // Appconfigurer객체를 통해 생성된 모든 인스턴스에 접근하여 사용한다.
    static class Appconfigurer{
        ProductRepository productRepository = new ProductRepository();
        //생성된 ProductRepository객체를 Menu와 Cart객체에 의존성주입하여 싱글톤 패턴 사용
        Menu menu = new Menu(productRepository);
        Cart cart = new Cart(productRepository, menu);
	
    //추상화와 다형성을 이용하여 Order생성자에 의존성주입을 해준 모습
        Discount[] discounts = new Discount[]{
                new RateDiscount(),
                new AmountDiscount()
        };
        Order order = new Order(cart, discounts);
    }
    
    
public static void main(String[] args) {
	//모든 객체가 모여있는 Appconfigurer의 인스턴스 생성
        Appconfigurer appconfigurer = new Appconfigurer();
	//이후 Appconfigurer의 생성된 인스턴스들에 접근하여 의존성주입을 한다.
        Kiosk kiosk = new Kiosk(
                appconfigurer.productRepository,
                appconfigurer.menu,
                appconfigurer.cart,
                appconfigurer.order
        );
    }
    

}

위와 같이 모든 인스턴스들이 Appconfigurer에서 생성되며 각각의 인스턴스들은 자신이 의존할 인스턴스들을 생성자를 통해 해당 인스턴스들의 참조값만을 전달받아 내부에 할당하게됩니다. 이와 같이 생성자를 통해 의존성 주입을 사용한 방법을 생성자 주입이라 한다.

 

또한 추상화와 다형성을 이용하여 관련 인스턴스를 하나의 타입으로 묶어 더 효율적으로 사용할 수있다. 

 

위의 대부분의 인스턴스들과 같이 직접적인 클래스의 타입에 의존하는것을 "구현에 의존한다"하며 Discount 인스턴스처럼 추상화에 의존하는것이 "역할에 의존한다"라고 한다. 

 

또한 이렇게 동일한 종류의 클래스들을 묶어 놓은것을 응집도라하며 응집도를 높이고 결합도를 낮출수록 각 객체들이 독립적인 기능을 더 잘 수행하게되며 수정이 용이해지는 장점이 있다.

 

의존성주입을 설명과 코드간의 비교만으로는 좀 복잡해 보일수 있지만 몇가지 단계만 거치면 가능하다.

  1. 직접 객체를 생성하는 클래스들을 찾아낸다.
  2. 해당 클래스내에서 의존성주입이 필요한 모든 객체(복사 생성자가 필요한 경우 제외)들을 필드에 선언하고 생성자를 이용하여 초기화해준다.
    • 역할 의존이 필요할경우 추상화와 다형성을 이용하여 해당 타입을 묶어준다.
  3. 설정파일 역할을 할 클래스(Appconfigurer)를 만들어준뒤 2번에 해당하는 모든 인스턴스를 생성해준다.
  4. 앱이 가동되기 전, 설정 클래스(Appconfigurer)의 인스턴스를 생성해준뒤 필요한 모든 인스턴스들을 앱을 가동하는 생성자에 의존성 주입을 해준다. 

 

'Java > Java' 카테고리의 다른 글

제네릭 (Generic)  (0) 2023.03.07
열거형 (Enum)  (0) 2023.03.06
복사 생성자와 싱글톤  (0) 2023.03.05
객체지향 프로그래밍 심화(다형성, 추상화)  (0) 2023.03.01
객체지향 프로그래밍 심화(상속, 캡슐화)  (0) 2023.02.28