0. 의존 관계란
- 의존 관계란 어떤 대상 A가 대상 B에 영향을 받는, 연관이 있는 관계이다.
(사용하는 관계면 의존한다라고 봐도 무방한 것 같다.)
- 예를 들자면, 인터페이스 A를 구현한 클래스 classA에서 인터페이스 B를 구현한 클래스 classB1을 사용한다.
public class classA implements A{
private B objectB = new classB1();
}
이러면 클래스 classA는 인터페이스 B와 구체 클래스 classB1에 의존하는 관계가 되는 것이다.
하지만 이렇게 의존 관계를 설정한다면 문제점이 있다.
0. SRP(단일 책임원칙) 위반
클래스 classA는 직접 구현 객체를 생성하고, 연결하고, 실행하는 다양한 책임을 가지고 있다.
하나의 클래스에는 하나의 책임만을 가져야 한다는 SRP(Single Responsibility Principle)을 위반하게 된다.
1. OCP(개방 폐쇄 원칙) 위반
만약 클래스 classA가 classB1을 사용하지 않고 다른 구체 클래스인 classB2로 수정하면 어떻게 될까?
클래스 classA의 코드에서 직접 classB1을 classB2로 수정해야 하고,
확장에는 열려 있고 수정에는 닫혀있어야 한다는 OCP(Open/Close Principle)를 위반하게 된다.
2. DIP(의존관계 역전 원칙) 위반
또한 현재 클래스 classA는 추상화에 의존하지 않고, 인터페이스를 구현한 클래스인 classB1에 의존하고 있다.
구체화된 것에 의존하지 말고, 추상화에 의존해야 한다는 DIP(Dependency Inversion Principle) 또한 위반이다.
따라서, 구체화 의존을 없애고 인터페이스(추상화)에 의존하게 끔 설계를 변경하면 된다.
public class classA implements A{
private B objectB;
}
이 코드는 추상화에만 의존하고 있다. 하지만 구현 클래스가 없는데 어떻게 작동이 될까 싶다.
실제로 Null Point Exception이 발생한다.
이 문제의 해결방안은 누군가가 클래스 classA에 인터페이스 B의 구현 객체를 대신 생성하고 주입해주면 된다.
1. 관심사의 분리
- 어플리케이션을 축구팀이라 생각하고, 각각의 인터페이스를 포지션이라고 생각해보자.
실제 포지션에 어떤 선수가 뛸지는 감독이 정한다.
위의 코드는 마치 A 포지션(인터페이스, 역할)에서 경기를 뛰는 선수 classA(클래스, 구현체)가 B 포지션에서 뛰는 classB1을 직접 정하는 것과 같다.
(classA에서 직접 classB1을 생성하기 때문이다.)
즉, classA는 경기도 뛰어야 하고, B 포지션 중 선수 classB1을 정하고 여러 가지 책임을 지닌 것과 같다.
- 관심사를 분리하면, 선수(Class)는 포지션 B에 있는 선수와 경기를 뛰는 것에만 집중해야 한다.
classA는 B 포지션에 어떤 선수가 선임돼도 경기를 뛸 수 있어야 한다.
따라서 외부에서 감독이 B포지션에 classB1을 발탁하고, classA와 같이 뛰도록 클래스 A에 넣어주어야 한다.
public class classA implements A{ // 생성자 주입
private final B objectB;
classA(B objectB){
this.objectB = objectB;
}
}
이렇게 되면 classA는 오로지 인터페이스 B에만 의존한다.
-> 의존 관계 역전 원칙 만족
그리고 외부에서 B의 구체 클래스를 생성해서 classA의 생성자가 실행될 때 파라미터로 넣어주면 의존 관계가 연결된다.
-> 구체 클래스를 바꿔도 클라이언트 코드 classA 자체에서 수정이 일어나지 않는다. 개방 폐쇄 원칙 만족
마지막으로, classA는 의존 관계를 외부에서 주입받기에 자신의 로직만을 실행한다.
-> 단일 책임 원칙 만족
이걸 classA 입장에서 보면 의존 관계를 마치 외부(AppConfig)에서 주입해주는 것 같다고 해서 DI(Dependency Injection), 의존 관계 주입이라고 부른다.
2. IOC(Inversion of Control) - 제어의 역전
우리가 프로그램을 개발할 때 보통 구현 객체 내에서 객체를 생성하고, 연결하고, 실행하는 등 프로그램의 흐름을 제어했다. 하지만 위에서 보았듯이 객체지향 원칙에 맞게 리팩터링 후 AppConfig(외부)에서 주입할 객체를 생성하고, 의존관계를 연결하고, 실행했다.
이후에 각자 역할을 수행하는 객체는 그냥 어떤 구현체가 들어오는지 몰라도, 외부에서 주입받아서 자신의 로직을 실행한다. 프로그램의 실행 흐름은 AppConfig에게로 위임된 것이다. AppConfig에서 다른 구현 객체를 만들어서 주입해도, 주입받는 객체는 그냥 자신의 일을 수행한다.
이렇게, 프로그램의 흐름을 직접 제어하는 것이 아닌, 외부에서 관리하는 것을 제어의 역전(IOC)라고 한다.
그리고 AppConfig와 같이 외부에서 의존관계를 주입해주는 것을 Ioc컨테이너 또는 DI 컨테이너라고 한다.
'개발 공부 > 스프링' 카테고리의 다른 글
스프링 - 의존 관계 주입 방법 4가지 (0) | 2021.09.17 |
---|---|
스프링 - @Component와 컴포넌트 스캔 (0) | 2021.09.17 |
스프링 - 스프링 컨테이너, 싱글톤 (0) | 2021.09.17 |
스프링 MVC - 서블릿을 통한 HTTP 요청(GET, POST, API, JSON) (0) | 2021.09.08 |
스프링 MVC - 웹 구조, 서블릿 (0) | 2021.09.04 |