의존성 주입 DI

qkrrnjswo·2023년 7월 21일
0

공부 정리

목록 보기
10/24

1. 객체 지향 관점에서 DI의 필요성

객체지향 프로그래밍이란 프로그램을 객체들의 협력과 결합으로 파악하고자 하는 프로그래밍의 패러다임이다.
그렇다면 어떻게 해야 객체 지향적으로 설계를 잘할 수 있을까?
2000년대 초 로버트 마틴이 제안한 SOLID 원칙이란 것이 있다.

  1. SRP 단일 책임 원칙
  2. OCP 개방-폐쇄 원칙
  3. LSP 리스코프 치환 원칙
  4. ISP 인테페이스 분리 원칙
  5. DIP 의존관계 역전 원칙

하지만 이 원칙에 따라 코드를 짜다보면 OCP와 DIP에서 문제가 발생한다.

public interface Product {
	public abstract void setPrice(int price);
	public abstract int getPrice();
}

-----------------------------------------------

public class Pencil implements Product {
	int price;
	@Override
	public void setPrice(int price) {
		this.price = price;
	}
	@Override
	public int getPrice() {
		return price;
	}
}

public class NoteBook implements Product {
	int price;
	@Override
	public void setPrice(int price) {
		this.price = price;
	}
	@Override
	public int getPrice() {
		return price;
	}
}

-----------------------------------------------

public class Store {
	//private Product product = new NoteBook();
	private Product product = new Pencil();
}

위 코드에서 Store가 Pencil이라는 구체화에 의존하고 있기 때문에 DIP를 위반하고 있다.
또한 다른 Product로 바꾸기 위해서는 Store의 코드를 고쳐야 하기 때문에 OCP도 위반하고 있다.

이 의존성 문제를 해결하기 위해서는 Store와 Pencil의 연결을 끊어 줘야한다.
하지만 그렇게 된다면, Pencil을 생성이 불가능하기 때문에 NullPointerException이 발생한다.

따라서, 위 그림처럼 누군가가 객체를 대신 생성을 해서 주입을 시켜줄 필요가 있다.


2. 의존성 주입(DI)이란?

외부에서 두 객체 간의 관계를 결정해 주는 디자인 패턴으로,
위 그림처럼 인터페이스를 사이에 둘 때 객체가 구체화에 의존하지 않게 해주고,
런타임 시에 관계를 동적으로 의존성을 주입해
프로그램의 유연성을 높여주고 결합도를 낮춰 객체 지향적으로 만들어 준다.

2-1. Field 주입

장점
1. 코드가 간결하다.

단점
1. 외부에서 접근이 불가능하다.
2. 프레임워크가 무조건 있어야 사용이 가능하다.
3. 프레임워크에 의존적이기 때문에 객체지향적이지 않다.

@Controller
public class MyController {
    @Autowired 
    private MyService myService;
}

2-2. Setter 주입

단점
1. Setter를 public으로 열어두어야 하기 때문에 주입된 객체가 언제든지 변경될 가능성이 있다.

@Controller
public class MyController {
    private MyService myService;
    
    @Autowired 
    public void setService(MyService myService) {
		this.myService = myService;
	}
}

--------------------------------------------------

public class Store {
	private Product product;
    
    public void setProduct(Product product) {
		this.product = product;
	}
}

2-3. 생성자 주입

장점
1. 생성자의 호출 시점에 1회 호출 되는 것이 보장한다.
2. 주입받은 객체가 변경될 가능성이 없다.
3. 생성자가 1개만 있을 경우에 @Autowired를 생략해도 주입이 가능하다.

@Controller
public class MyController {
    private MyService myService;
    
    //@Autowired
    public void MyController(MyService myService) {
		this.myService = myService;
	}
}

--------------------------------------------------

public class Store {
	private Product product;
    
    public Store(Product product) {
		this.product = product;
	}
}

3. Spring에서 생성자 주입 방식 사용해야하는 이유

스프링 공식 문서에서 생성자 주입 방식 사용을 정식으로 권고하고 있는데 그 이유는 다음과 같다.

3-1. 순환 참조 에러 방지

생성자 주입의 경우 애플리케이션 구동 시점에서 순환 참조 에러를 감지할 수 있다.
즉, Bean에 등록하기 위해 객체를 생성하는 과정에서 순환 참조가 발생한다.

@Service
public class NewService {

	@Autowired
	private MyService myService;
}

-------------------------------------------

@Service
public class MyService {

	@Autowired
	private NewService newService;
}

3-2. 객체의 불변성 확보

생성자 주입 시 final 선언이 가능하기 때문에, 런타임 시 주입받는 객체가 변할 가능성이 없다.

@Controller
public class MyController {
    private final MyService myService;
    
    //@Autowired
    public void MyController(MyService myService) {
		this.myService = myService;
	}
}

3-3. 테스트 코드의 작성 용이

테스트 코드를 좀 더 편리하게 작성이 가능하다.

public class MyService {
    private final MyRepository myRepository;
    
    public MyService(MyRepository myRepository) {
    	this.myRepository = myRepository;
    }
    
    public void testFuntion() {
        //테스트 할 기능
    }
}
 
public class MyServiceTest {
    MyRepository myRepository = new MyRepository();
    MyService myService = new MyService(myRepository);
    
    @Test
    public void test() {
        myService.testFuntion();
    }
}

4. 참고

https://mangkyu.tistory.com/150
https://mangkyu.tistory.com/125
https://dev-coco.tistory.com/70

0개의 댓글