ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 의존성 주입(Dependency Injection)
    개발공부 2023. 5. 30. 21:09

    의존성 주입(Dependency Injection, DI)

    DI는 의존성 주입, 의존관계 주입이라고 하며, 제어의 역전(IoC)의 방법 중 하나이다.

    단어로 풀어서 보면 Dependency(의존 관계)를 생성하는게 아닌 외부에서 Injection(주입) 한다는 것이다.

    그렇다면 의존 관계는 무엇이고, 주입한다는건 어떤 의미인가?

     

    의존

    어떤 객체가 다른 객체를 생성하거나, 다른 객체의 메서드를 호출하는 행위를 할 때, 다른 객체에 의존한다 라고 한다.

    예시를 들자면 

    interface Tire{
    	String getBrand();
    }
    
    
    public class KoreaTire implements Tire{
    	public String getBrand(){
        	return "한국 타이어";
        }
    }
    
    public class Car{
    	Tire tire;
        
        public Car(){
        	tire = new KoreaTire();
        }
        public String getTireBrand(){
        	return "현재 타이어 : " + tire.getBrand();
        }
    }
    
    public class Driver{
    	public static void main(String[] args){
        	Car car = new Car();
            System.out.print(car.getTireBrand());
        }
    }

    Tire 인터페이스를 만들고, 해당 인터페이스를 구현한 KoreaTire가 Car 클래스에서 new를 통해 객체가 생성되며 의존관계가 일어났다. 또한 Driver 역시 Car와 의존 관계에 있다고 할 수 있다.

    이렇게 의존 관계를 가지고 있다면 의존하고 있는 객체가 바뀔때, 해당 객체를 사용하는 자신도 영향을 받을 수 있다.

    즉, 토비의 스프링에서 말하는 '의존 대상 B가 변하면 그것이 A에도 영향을 미친다'는 의미이다. 그렇기 때문에 의존성을 가지고 있다면(객체 내부에서 다른 객체를 생성하면) 결합도가 강해지기 때문에 유연성이 떨어지게 된다. 변하기 쉬운 객체에 의존한다면 OOP SOLID 원칙의 의존 역전 원칙과도 어긋나게 된다.

     

    주입

    주입은 클래스의 외부에서 객체를 생성하여 생성한 객체를 클래스 내부에 주입하는 것을 의미한다.

    하나의 객체를 각각의 클래스에서 생성하여 사용한다고 치면, 수정사항이 생기거나 문제가 생겼을 때 각각의 클래스를 다 수정하고 관리해야한다. 그러나 외부에서 인스턴스를 만들어 해당 인스턴스가 필요한 객체들에 주입을 해줄 경우, 해당 인스턴스만을 수정해 각각의 클래스에서 필요한 내용이 수정할 수 있다.

     

    그래서, 의존성 주입이란?

    의존관계를 객체와 객체끼리 직접 맺는것이 아니라, 외부의 요인을 통해서 주입받는것을 의미한다.

    클래스간의 의존 관계가 고정되지 않게 하며, 유연성이 올라가고 결합도는 내려가는 것이다.

    이를 통해 객체 생성,사용에 관심사를 분리하여 로직에 집중할 수 있고, 가독성과 코드 재사용성 또한 올라간다.

    특히, 클래스간의 결합도가 약해지면 특정 클래스만을 테스트하기 편해지므로 TDD, 리팩토링 등에 강점을 가질 수 있다.

     

    의존성 주입의 방법

    생성자를 통한 의존성 주입

    public class Car{
    	Tire tire;
        
        public Car(Tire  tire){
        	this.tire = tire;
        }
        public String getTireBrand(){
        	return "현재 타이어 : " + tire.getBrand();
        }
    }

    위의 예시에서, 생성자에서 new koreaTire를 만드는것이 아니라, Tire를 구현한 어떤 객체를 변수로 받는 생성자를 만든 의존성 주입 방법이다. 이렇게 함으로서 한국 타이어가 아닌 미국, 독일, 일본 등 어떤 나라의 타이어가 있던 Tire 인터페이스를 구현한 클래스라면 Car 클래스에 변화 없이 사용할 수 있게 되어 확장성, 유연성을 갖게 된다.

     

    속성, 수정자를 이용한 방법 (setter)

    public class Car{
    	Tire tire;
        public Tire getTire(){
        	return tire;
        }
        public void setTire(Tire tire){
        	this.tire = tire;
        }
        public String getTireBrand(){
        	return "현재 타이어 : " + tire.getBrand();
        }
    }
    
    
    public class Driver{
    	public static void main(Stirng[] args){
        	Tire tire = new KoreaTire();
            Car car = new Car();
            car.setTire(tire);
            
            System.out.println(car.getTireBrand());
        }
    }

    이 경우 Car의 생성자가 사라지고, Tire 속성에 대한 Getter, Setter (접근자, 설정자) 가 생겼으며 Driver 클래스에서도 변화가 생긴다.

    스프링을 이용한 방법 ( xml, 필드 객체 선언 등)

    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    public class Driver{
    	ApplicationContext context = new ClassPathXmlApplicationContext("expert002/expert002.xml");
        Car car = context.getBean("car", Car.class);
        Tire tire = context.getBean("tire", Tire.class);
        car.setTire(tire);
        System.out.println(car.getTireBrand());
    }

    xml을 이용한 방법은 xml 파일에 bean을 지정하여 등록하고, 지정된 bean을 사용하여 꺼내올 수 있다.

    이 경우, xml 파일을 수정함으로서 타이어를 교체할 수 있으므로 자바 코드의 수정, 재컴파일, 재배포가 없이 수정이 가능하다. 또한, Car 에대한 bean에 property 값을 지정한다면 Tire bean을 찾아오고 setter를 사용하지 않고도 사용할 수 있다.

     

    import org.springframework.beans.factory.annotaion.Autowired;
    
    @Autowired
    Tire tire;

     

    필드 주입 방법은 필드에 바로 어노테이션을 사용해 주입하는 방법이다. 

    bean에 등록이 되어있던, 스프링 내에서 참조를 하여 사용한다. 편리하고 보기 편하다고 많이 사용되나, 인텔리제이에서는 Autowired를 사용하면 '항상 생성자를 통한 의존성 주입을 권장한다' 고 되어있다.

     

    왜 생성자를 통해 의존성 주입을 해야하는가?

    1. 의존성을 주입할때 final을 선언해 객체가 변할 일 없이 설계할 수 있다.

    2. Autowired를 남용하면 SOLID 원칙의 단일 책임 원칙이 위반될 수 있다.

    3. 생성자/필드주입과 빈 주입 단계가 다르기 때문에, 의존성이 주입되지 않아 발생되는 NullPointException을 방지할 수 있다.

    4. 주입 단계가 다르기 때문에 순환참조를 어플리케이션 실행 전에 파악 가능

    5. 다른 의존성 주입일때는 테스트코드 작성시 bean이 주입되지 않는다. 그래서 스프링의 빈이나 설정을 가져와서 환경을 만들어 테스트해야 하지만 생성자로 주입하는 경우 테스트코드 자체에 필요한 정보만을 넣고 테스트 할 수 있다.

     

    위와 같은 이유들로 생성자를 사용한 DI가 권장되지만, 필드 주입이 가독성이 좋고 보기 편하기 때문에 스프링을 공부할때는 필드 주입이 자주 사용된다.

     

    정리

    의존성 주입은 객체가 의존하는 다른 객체를 외부에서 선언후 주입해 사용하는 것을 의미한다.

    이를 통해 얻는 장점은 의존성이 줄어들어 코드를 수정할 일이 생길때 다른 코드도 수정할 필요가 없어지고 가독성, 재사용성이 높아지며 단위테스트에 유리해진다.

    '개발공부' 카테고리의 다른 글

    SPA (Single Page Application), MPA(Multiple Page Application)  (0) 2023.06.08
    AOP란?  (0) 2023.06.04
    JWT(JSON Web Token) 이란  (1) 2023.05.31
    SQL Injection이란?  (0) 2023.05.31
    안녕하세요  (0) 2023.05.30
Designed by Tistory.