IoC (Inversion of Control)
제어의 역전이라는 뜻으로, 전통적인 프로그래밍에서는 개발자가 프로그램의 흐름과 제어를 직접 다루는 반면, IoC는 프레임워크가 객체의 생성, 관리, 제어 흐름을 담당하도록 변경하는 개념이다. Spring은 이를 지원하기 위해 ApplicationContext라는 컨테이너를 제공한다.
DI (Dependency Injection)
의존성 주입. 하나의 객체에 다른 객체의 의존성을 제공하는 기술. 장점은 다음과 같다.
- 코드의 재활용성을 높여 유지보수가 용이해진다.
- 클래스 간 결합도를 낮출 수 있다.
- 인터페이스 기반으로 설계되며, 코드를 유연하게 한다.
- 단위 테스트하기 더 쉬워진다.
IoC와 DI는 좋은 코드 작성을 위한 Spring의 핵심 기술 중 하나로, IoC는 설계 원칙에 해당하고 DI는 디자인 패턴에 해당한다.
의존성이란?
[예시1] 강하게 결합되어 있는 Consumer와 Chicken
public class Consumer {
void eat() {
Chicken chicken = new Chicken();
chicken.eat();
}
public static void main(String[] args) {
Consumer consumer = new Consumer();
consumer.eat();
}
}
class Chicken {
public void eat() {
System.out.println("치킨을 먹는다.");
}
}
위 코드는 실행되는데 있어 아무런 문제는 없지만 만약 Consumer가 치킨이 아니라 피자나 다른 음식을 먹고 싶어 한다면 수 많은 코드의 변경이 필요하게 된다. 이를 강하게 결합되어 있다고 하는데, 결합을 약하게 만들기 위해서는 Java의 interface를 활용하면 해결할 수 있다.
[예시2] interface 다형성의 원리를 사용한 약한 결합 및 약한 의존성
public class Consumer {
void eat(Food food) {
food.eat();
}
public static void main(String[] args) {
Consumer consumer = new Consumer();
consumer.eat(new Chicken());
consumer.eat(new Pizza());
}
}
interface Food {
void eat();
}
class Chicken implements Food{
@Override
public void eat() {
System.out.println("치킨을 먹는다.");
}
}
class Pizza implements Food{
@Override
public void eat() {
System.out.println("피자를 먹는다.");
}
}
제어의 역전이란?
이전에는 Consumer가 직접 Food를 만들어 먹었기 때문에 새로운 Food를 만들려면 추가적인 요리준비 (코드 변경)가 불가피했다.
=> Consumer -> Food
이를 해결하기 위해 만들어진 Food를 Consumer에게 전달해주는 식으로 변경함으로써 Consumer는 추가적인 요리준비 (코드변경) 없이 어느 Food가 되었든지 전부 먹을 수 있게 되었다.
=> Food -> Consumer로 제어의 흐름이 역전
예시) 강한 결합의 메모장 프로젝트 개선
1. Contoller1 이 Service1 객체를 생성하여 사용
public class Controller1 {
private final Service1 service1;
public Controller1() {
this.service1 = new Service1();
}
}
2. Service1 이 Repostiroy1 객체를 생성하여 사용
public class Service1 {
private final Repository1 repository1;
public Service1() {
this.repository1 = new Repository1();
}
}
3. Repostiroy1 객체 선언
public class Service1 {
private final Repository1 repository1;
public Service1() {
this.repository1 = new Repository1();
}
}
4. 만약, 다음과 같이 변경된다면..
- Repository1 객체 생성 시 DB 접속 id, pw 를 받아서 DB 접속 시 사용
- 생성자에 DB 접속 id, pw 를 추가
public class Repository1 {
public Repository1(String id, String pw) {
// DB 연결
Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/memo", id, pw);
}
}
강한 결합의 문제점
- Controller 5 개가 각각 Service1 을 생성하여 사용 중
- Repository1 생성자 변경에 의해..
- ⇒ 모든 Contoller 와 모든 Service 의 코드 변경이 필요!
- MemoService와 MemoController에서 자신은 사용하지 않지만 MemoRepository에서 사용하기 위해 생성자에 JdbcTemplate을 불필요하게 넣어줌.
public MemoController(JdbcTemplate jdbcTemplate) {
this.memoService = new MemoService(jdbcTemplate);
}
...
public MemoService(JdbcTemplate jdbcTemplate) {
this.memoRepository = new MemoRepository(jdbcTemplate);
}
강한 결합 해결방법
- 각 객체에 대한 객체 생성은 딱 1번만!
- 생성된 객체를 모든 곳에서 재사용!
- 생성자 주입을 사용하여 필요로 하는 객체에 해당 객체 주입!
1. Repository1 클래스 선언 및 객체 생성 → repository1
public class Repository1 { ... }
// 객체 생성
Repository1 repository1 = new Repository1();
2. Service1 클래스 선언 및 객체 생성 (repostiroy1 사용) → service1
Class Service1 {
private final Repository1 repitory1;
// repository1 객체 사용
public Service1(Repository1 repository1) {
// this.repository1 = new Repository1(); (X)
this.repository1 = repository1;
}
}
// 객체 생성
Service1 service1 = new Service1(repository1);
3. Contoller1 선언 ( service1 사용)
Class Controller1 {
private final Service1 service1;
// service1 객체 사용
public Controller1(Service1 service1) {
this.service1 = new Service1();
this.service1 = service1;
}
}
4. 만약, 다음과 같이 변경된다면,
- Repository1 객체 생성 시 DB 접속 id, pw 를 받아서 DB 접속 시 사용
- 생성자에 DB 접속 id, pw 를 추가
public class Repository1 {
public Repository1(String id, String pw) {
// DB 연결
Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/memo", id, pw);
}
}
// 객체 생성
String id = "root";
String pw = "1234";
Repository1 repository1 = new Repository1(id, pw);
[개선 결과]
⇒ Repository1 생성자 변경은 이제 누구에게도 피해(?) 를 주지 않음
⇒ Service1 생성자가 변경되면? 모든 Contoller → Controller 변경 필요 X
결론적으로, 강한 결합 ⇒ 느슨한 결합
public MemoController(MemoService memoService) {
this.memoService = memoService;
}
...
public MemoService(MemoRepository memoRepository) {
this.memoRepository = memoRepository;
}
- 이제는 더 이상 MemoController와 MemoService에서 사용하지도 않는 JdbcTemplate을 주입 받아와 MemoRepository애 주입해줄 필요가 없어졌다.
제어의 역전
프로그램의 제어 흐름이 뒤바뀜
- 강한 결합 상태였던 Controller ⇒ Service ⇒ Repository 를
- DI 즉, 의존성 주입을 통해 Repository ⇒ Service ⇒ Controller 로 역전함으로써 효율적인 코드로 바꾸었다.
[참고 자료]
https://velog.io/@ashwon1218/DIDependency-injection-%EB%9E%80
DI(Dependency injection) 란?
DI는 Dependency injection의 준말로 '의존성 주입' 이라는 뜻을 가진다. 소프트웨어 공학에서 말하는 의존성 주입은 하나의 객체에 다른 객체의 의존성을 제공하는 기술이라고 표현한다. '의존 관계'
velog.io
[기술 면접] Spring IoC (Inversion of Control)와 DI (Dependency Injection)에 대하여
Spring을 사용하며 IoC와 DI라는 말을 굉장히 많이 들어봤고, 그것을 활용한다는 말을 굉장히 많이 들어왔는데 IoC와 DI가 정확히 무엇이고, 어떤 식으로 활용되는지는 자세히 알지 못한다. 오늘은
velog.io
'내배캠 > TIL' 카테고리의 다른 글
SQL INSTR() 함수 (0) | 2024.08.14 |
---|---|
예외 처리 (0) | 2024.08.13 |
오브젝트와 의존관계 (0) | 2024.08.09 |
DTO, DAO, VO (0) | 2024.08.08 |
Git .gitignore 적용하기 (0) | 2024.08.07 |