<< 학습 목표 >>
1. 의존에 대해서 설명할 수 있다.
2. 의존 객체에 대해서 설명할 수 있다.
3. 의존 주입에 대해서 설명할 수 있다.
우선 프로젝트 -> com.example.de.chapter03 패키지를 추가하자
Spring Framework에서 중요한 개념인 DI (Dependency Injection) 에 대해서 알아보자
DI는 우리말로 의존 주입이라고 함
DI는 Spring Framework에서 사용되는 용어가 아닌 개발에서 일반적으로 사용하는 말이고 일반적으로 사용되는 기법임
먼저 의존에 대해서 알아보자
우리 말에서 "A가 B에게 의존한다" 라면 "A는 B에게 전적으로 도움을 받고 있다" 로 해석할 수 있음
B가 없으면 A가 스스로 무언가를 할 수 없을 것
좀 더 현실의 예를 들어보자
대부분이 가전제품은 전기에 의존하고 있음
컴퓨터는 전기에 의존하고 있기 때문에 전기가 없다면 컴퓨터를 켤 수 없음
스마트폰은 배터리에 의존하고 있기 때문에 배터리가 없다면 스마트폰을 켤 수 없음
시계는 건전지에 의존하고 있기 때문에 건전지가 없다면 시계는 동작하지 않음
이런것처럼 프로그래밍에서도 의존 현상이 발생하고 이는 중요한 개발 기법 중 하나임
Spring Framework으로 의존 기법을 설명하면 좋겠지만 난이도가 굉장히 어려워지니 자바로 의존 기법을 이해하자
Spring Framework는 자바 언어를 사용하는 프레임워크이므로 자바로 의존 기법을 이해했다면 Spring Framework의 의존, 의존 주입 등을 이해할 수 있음
우선 캡슐화부터 시작해서 의존, 의존 주입(DI), 제어 역전(IoC)까지 천천히 그리고 확실하게 알아보자
상속까지 들어가야 할 수도 있지만 상속까지 끼게되면 이해하는데 더 복잡해지므로 상속은 제외하고 보자
위에서 언급한대로 잠깐 여기서는 Spring Framework는 접어두고 자바로만 알아보자
아래와 같이 스마트폰을 구현한 클래스들이 있음
이 클래스들을 프로젝트 -> com.study.chatper03 패키지에 추가하자
이번에는 프로젝트 -> com.study.chapter03 -> Ex01 클래스를 아래와 같이 추가하자
Ex01 클래스는 스마트폰들을 생성하고 스마트폰들의 정보를 출력하는 역할임
package com.study.chapter03;
public class Ex01 {
public static void main(String[] args) {
GalaxyS23 phone1 = new GalaxyS23();
phone1.setBatteryType("건전지");
GalaxyS23 phone2 = new GalaxyS23();
phone2.setBatteryType("건전지");
GalaxyFlip4 phone3 = new GalaxyFlip4();
phone3.setBatteryType("건전지");
IPhone14 phone4 = new IPhone14();
phone4.setBatteryType("건전지");
IPhone14 phone5 = new IPhone14();
phone5.setBatteryType("건전지");
System.out.println("<< phone1 >>");
System.out.println("배터리는 " + phone1.getBatteryType() + " 입니다.");
System.out.println("배터리를 모두 사용했다면 " + phone1.getBatteryType() + " 를 교체하세요.");
System.out.println("<< phone2 >>");
System.out.println("배터리는 " + phone2.getBatteryType() + " 입니다.");
System.out.println("배터리를 모두 사용했다면 " + phone2.getBatteryType() + " 를 교체하세요.");
System.out.println("<< phone3 >>");
System.out.println("배터리는 " + phone3.getBatteryType() + " 입니다.");
System.out.println("배터리를 모두 사용했다면 " + phone3.getBatteryType() + " 를 교체하세요.");
System.out.println("<< phone4 >>");
System.out.println("배터리는 " + phone4.getBatteryType() + " 입니다.");
System.out.println("배터리를 모두 사용했다면 " + phone4.getBatteryType() + " 를 교체하세요.");
System.out.println("<< phone5 >>");
System.out.println("배터리는 " + phone5.getBatteryType() + " 입니다.");
System.out.println("배터리를 모두 사용했다면 " + phone5.getBatteryType() + " 를 교체하세요.");
}
}
현재 모든 스마트폰의 배터리가 건전지인 상태라고 하자
그래서 스마트폰 5대를 생성했고 모든 스마트폰의 배터리를 건전지로 설정한 상태임
그리고 스마트폰의 배터리 정보를 출력하고 있음
이때 배터리 정보를 출력하는 문구가 조금이라도 바뀌면 모든 Sysout에서 코드를 바꿔야하므로 메서드를 활용하면 스마트폰의 배터리 정보를 좀 더 쉽게 출력할 수 있고 좀 더 쉽게 수정할 수 있음
각 스마트폰 클래스에 다음과 같이 배터리 정보를 출력하는 메서드를 추가하자
이제 바뀐 방식대로 배터리 정보를 출력하도록 바꿔보자
package com.study.chapter03;
public class Ex01 {
public static void main(String[] args) {
GalaxyS23 phone1 = new GalaxyS23();
phone1.setBatteryType("건전지");
GalaxyS23 phone2 = new GalaxyS23();
phone2.setBatteryType("건전지");
GalaxyFlip4 phone3 = new GalaxyFlip4();
phone3.setBatteryType("건전지");
IPhone14 phone4 = new IPhone14();
phone4.setBatteryType("건전지");
IPhone14 phone5 = new IPhone14();
phone5.setBatteryType("건전지");
System.out.println("<< phone1 >>");
phone1.printBatteryInfo();
System.out.println("<< phone2 >>");
phone2.printBatteryInfo();
System.out.println("<< phone3 >>");
phone3.printBatteryInfo();
System.out.println("<< phone4 >>");
phone4.printBatteryInfo();
System.out.println("<< phone5 >>");
phone5.printBatteryInfo();
}
}
여기서 스마트폰은 배터리(batteryType 멤버 변수)에 의존하고 있음
따라서 배터리는 스마트폰의 의존 객체임
그리고 main이 스마트폰의 의존 객체를 setter 통해서 넣어주고 있음
스마트폰이 의존 하고 있는 batteryType 멤버 변수에 건전지 문자열을 직접 넣는게 아니라 main이 setter 통해 넣어주고 있는데 이를 의존 주입이라고 함
다시 설명하면 본인이 아닌 제 3자가 의존 객체를 넣어주는걸 의존 주입(Dependency Injection) 이라고 함
의존 주입을 하는 방법은 두 가지가 있음
- 생성자를 통한 의존 주입
- setter를 통한 의존 주입
지금 우리는 setter를 통한 의존 주입이므로 다른 방식의 의존 주입도 해보자
아래와 같이 생성자를 통한 의존 주입으로 바꿔보자
변경된 의존 주입 방식을 적용해 스마트폰에 배터리를 지정하자
package com.study.chapter03;
public class Ex01 {
public static void main(String[] args) {
GalaxyS23 phone1 = new GalaxyS23("건전지");
GalaxyS23 phone2 = new GalaxyS23("건전지");
GalaxyFlip4 phone3 = new GalaxyFlip4("건전지");
IPhone14 phone4 = new IPhone14("건전지");
IPhone14 phone5 = new IPhone14("건전지");
System.out.println("<< phone1 >>");
phone1.printBatteryInfo();
System.out.println("<< phone2 >>");
phone2.printBatteryInfo();
System.out.println("<< phone3 >>");
phone3.printBatteryInfo();
System.out.println("<< phone4 >>");
phone4.printBatteryInfo();
System.out.println("<< phone5 >>");
phone5.printBatteryInfo();
}
}
의존 주입에 좋은 방식, 더 나은 방식은 없음
그저 두 가지 방식이 있을 뿐이고 상황에 따라서 필요한 또는 하고 싶은 방식으로 의존 주입을 하면 됨
여기까지 의존이 무엇이고 의존 주입이 무엇인지 설명을 했음
지금 의존 주입을 하나 하나 일일히 해주고 있음
이렇게 했을 때 생길 수 있는 문제점은 의존 객체가 반드시 바뀌어야할 때 개발자가 직접 바꿔줘야하는 의존 객체들이 너무 많기 때문에 개발자의 실수로 의존 객체가 바뀌지 않을 수 있다는 것
개발자의 실수로 의존 객체가 바뀌지 않는 상황을 알아보자
이제 시대가 발전해서 스마트폰이 건전지 대신 리튬이온 배터리로 바꼈음
그렇다면 main에서 스마트폰에 건전지를 주입해주는 대신 리튬이온 배터리를 주입해주도록 해야함
중간에 바로 보이지만 의존 객체가 반드시 바뀌어야하는데 의존 객체를 바꿔줘야 할 스마트폰들이 너무 많기 때문에 실수로 어떤 객체의 의존 객체를 바꾸지 못할 수도 있다는 것
이 문제를 해결할 수 있는 첫 번째 방법은 스마트폰이 의존 객체를 직접 주입하는 것
아래와 같이 스마트폰의 생성자를 통해서 의존 주입을 받는게 아닌 스마트폰이 직접 필요한 의존 객체(batteryType 멤버 변수)를 주입하면 됨
아래와 같이 따라 입력할 필요는 없고 보기만 하면 됨
바뀐 의존 주입 방법대로 스마트폰들을 생성해보자
이렇게 하면 개발자의 실수를 없앨 수 있는것처럼 보임
기존에는 생성된 스마트폰 마다 의존 객체를 바꿔줘야했으므로 5대의 스마트폰이 있다면 5번 의존 객체를 바꿔줘야하고 100대의 스마트폰이 있다면 100번 의존 객체를 바꿔줘야했지만 스마트폰이 의존 객체를 직접 생성하도록 바꿈으로써 스마트폰의 대수에 상관 없이 세 종류의 스마트폰의 생성자를 수정하면 되므로...
그러나 바로 위에서 언급한것처럼 없앨 수 있는 것처럼 보이는거지 없앤건 아님
스마트폰의 종류가 늘어나면 어떻게 될까?
결국 100대의 스마트폰이 있는 상황과 100종류의 스마트폰이 있는 상황은 똑같은 상황이 됨
스마트폰이 직접 의존 객체를 생성하는 방식을 강한 결합 이라고 부르고 스마트폰이 의존 객체를 주입 받는걸 약한 결합이라고 부름
강한 결합은 좋지 못한 코드를 만들 확률이 높아지게 만들고 약한 결합은 좋은 코드를 만들 확률이 높아지게 만듬
다시 원래대로 생성자 또는 setter 를 사용해 의존 주입을 받는 약한 결합으로 되돌아가자
어차피 되돌아갈 것이므로 따라 입력하지는 말라고 했던 것
다시 원점으로 돌아와서 의존 객체를 주입하는 약한 결합이 좋은데 약한 결합을 하면 개발자의 실수로 의존 객체를 반드시 바꿔야하는데 바꾸지 못하는 상황이 생김
이를 해결할 수 있는 좋은 방안은 없을까?
당연히 있음
이를 해결할 수 있는 좋은 방안은 main에서 스마트폰을 직접 생성하지 말고 스마트폰을 생성해주는 공장을 만드는 것
프로젝트 -> com.study.chapter03 -> SmartPhoneFactory 클래스를 추가하고 아래 코드를 추가하자
package com.study.chapter03;
public class SmartPhoneFactory {
public Object createSmartPhone(String smartPhoneType) {
String batteryType = "리튬이온 배터리";
if(smartPhoneType.equals("GalaxyS23")) {
return new GalaxyS23(batteryType);
} else if(smartPhoneType.equals("GalaxyFlip4")) {
return new GalaxyFlip4(batteryType);
} else if(smartPhoneType.equals("iPhone14")) {
return new IPhone14(batteryType);
} else {
System.out.println("생산할 수 없는 스마트폰입니다.");
return null;
}
}
}
위와 같이 스마트폰을 만들고 필요한 의존 주입까지된 스마트폰을 반환해주는 공장이 있다면 개발자의 실수를 상당히 줄 일 수 있음
<< 코드 설명 >>
(1). 반환 타입이 Object 인건 스마트폰 공장에서 여러 종류의 스마트폰을 만들 수 있으므로 모든 종류의 스마트폰을 반환 할 수 있도록 하기 위함
혹시 반환 타입이 Object인 메서드 또는 매개변수 타입이 Object인 메서드에 대해서 잘 모른다면 자바 쪽이 약한것이므로 [ 반환 타입이 Object인 메서드 ], [ 매개변수 타입이 Object인 메서드 ] 로 검색해서 찾아보자
(2). 스마트폰에 필요한 의존 객체 생성
(3). 필요한 스마트폰을 생성해 의존 주입 후 반환
(4). 오타나 어떤 실수로 잘못된 스마트폰 타입을 전달했다면 이를 알려주는 else 문
이제 main에서는 스마트폰을 직접 생산하는게 아니라 SmartPhoneFactory 를 사용해서 필요한 스마트폰을 생산하면 됨
package com.study.chapter03;
public class Ex01 {
public static void main(String[] args) {
SmartPhoneFactory sf = new SmartPhoneFactory();
GalaxyS23 phone1 = (GalaxyS23) sf.createSmartPhone("GalaxyS23");
GalaxyS23 phone2 = (GalaxyS23) sf.createSmartPhone("GalaxyS23");
GalaxyFlip4 phone3 = (GalaxyFlip4) sf.createSmartPhone("GalaxyFlip4");
IPhone14 phone4 = (IPhone14) sf.createSmartPhone("iPhone14");
IPhone14 phone5 = (IPhone14) sf.createSmartPhone("iPhone14");
System.out.println("<< phone1 >>");
phone1.printBatteryInfo();
System.out.println("<< phone2 >>");
phone2.printBatteryInfo();
System.out.println("<< phone3 >>");
phone3.printBatteryInfo();
System.out.println("<< phone4 >>");
phone4.printBatteryInfo();
System.out.println("<< phone5 >>");
phone5.printBatteryInfo();
}
}
<< 코드 설명 >>
(1). 스마트폰을 만들기 위한 공장 생성
(2). 공장을 통해 필요한 스마트폰을 생성하는데 공장에서는 Object 타입으로 반환하므로 필요한 적절한 형태로 형변환
특히, 이제 main이 직접 스마트폰을 생성하는 방식에서 SmartPhoneFactory가 대신 스마트폰을 생성해주므로 이를 제어의 역전 ( IoC / Inversion Of Controll ) 이라고 부름
이제 코드가 상당히 완전해졌음
시대가 더 발전해 모든 스마트폰의 배터리를 리튬이온 배터리에서 리튬 폴리머 배터리로 바꿔야한다면 간단하게 스마트폰 생산 공장에서 배터리 타입만 바꿔주면 됨
main을 보면 처음(왼쪽)에는 스마트폰을 직접 생산하고 직접 의존 주입을 해줬지만 이제(오른쪽)는 스마트폰 공장에서 의존 주입까지 완료된 스마트폰을 받는 방식으로 바꼈음
이를 제어 역전(IoC / Inversion Of Control) 이라고 함
Spring Framework 를 배우다 갑자기 의존, 의존 주입(Dependency Injection / DI) 를 하는 이유는 Spring Framework의 핵심 기능 중 하나가 ( IoC, DI )임
Spring Framework에서 의존, 의존 주입, 제어 역전은 굉장히 중요한 키워드이니 반드시 내것으로 만들자
프로젝트를 개발하다 보면 컨트롤러와 서비스, 서비스와 DAO 등 많은 요소들이 서로 의존하며 동작하게됨
이때 마다 개발자가 직접 의존 주입을 하는게 아닌 SmartPhoneFactory 처럼 Spring Framework의 DI 를 사용해 의존 주입 된 요소를 받아서 사용하면 개발자의 실수도 줄어들고 개발도 편리해짐
이 글에서 만든 SmartFactory 를 컨테이너라고 부름
컨테이너는 프로젝트에 필요한 인스턴스를 생성해 반환해주는 역할을 함
또한 생성할 인스턴스에 의존 주입이 필요하면 의존 주입까지 해줌
이 글에서는 컨테이너를 우리가 직접 만들었지만 다음 글 ( https://codingaja.tistory.com/112 ) 에서는 Spring Framework의 DI를 사용해보자
의존, 의존 주입과 관련되서 다른 시각으로 이해하고 싶다면 아래 블로그를 더 보자
의존관계 주입(Dependency Injection) 쉽게 이해하기
이번 글에서는 DI(의존성 주입, 의존관계 주입)의 개념을 설명한다.
tecoble.techcourse.co.kr
의존성 주입이란 무엇이며 왜 필요한가?
목표 의존성 주입이 무엇인지 이해한다. 의존성 주입이 왜 필요한지 이해한다. 의존성 주입이란? 의존성 주입이란 클래스간 의존성을 클래스 외부에서 주입하는 것을 뜻한다. 더 자세하게는 의
kotlinworld.com
'Spring + Boot > Boot-Chapter03' 카테고리의 다른 글
Chapter03. Spring Boot - DI 심화 / Dependency Injection (0) | 2023.05.15 |
---|---|
Chapter03. Spring Boot - DI 기본 / Dependency Injection (0) | 2023.04.22 |