지금까지 spring boot 기초를 공부하면서 객체를 만든 적이 없다.
spring boot가 제공하는 IoC Container에서 controller, service, repository 등의 다양한 객체를 만들고 관리해주었기 때문이다.
필요에 따라 새로운 객체가 주입될 수 있는데 그것 또한 Container에서 관리한다.
프로그래머의 코드가 아닌 외부에 의해 제어되는것을 Inversion of Control 이라고 하고,
필요한 객체를 외부에서 코드로 주입하는 것을 Dependency Injection이라고 한다.
이 두가지를 통해 더욱 유연한, 객체지향적인 코드를 만든다.
사진 출처 : https://www.youtube.com/watch?v=5d3wEN0_MLY&t=743s
DI가 필요한 상황을 만들고,
IoC에 객체를 등록하고 사용하는 방법을 실습해보겠다.
public class Chef {
public String cook(String menu) {
// 재료 준비
Pork pork = new Pork("한돈 등심");
// 요리 반환
return pork.getName() + "으로 만든 " + menu;
}
}
class ChefTest {
@Test
void 돈까스_요리하기() {
// 준비
Chef chef = new Chef();
String menu = "돈까스";
// 수행
String food = chef.cook(menu);
// 예상
String expected = "한돈 등심으로 만든 돈까스";
// 검증
assertEquals(expected, food);
}
}
public class Pork {
private String name;
public Pork(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
현재 코드는 "재료"와 "셰프"의 의존성이 크다.
= 요구사항 변경에 취약한 코드 이다.
따라서 셰프와 재료 사이에 "조달 공장"을 두어 의존성을 낮추겠다.
= 요구사항 변경에 유연하도록 코드를 개선 하겠다.
IngredientFactory ingredientFactory = new IngredientFactory();
Chef chef = new Chef(ingredientFactory);
class ChefTest {
@Test
void 돈까스_요리하기() {
// 준비
IngredientFactory ingredientFactory = new IngredientFactory();
Chef chef = new Chef(ingredientFactory);
String menu = "돈까스";
// 수행
String food = chef.cook(menu);
// 예상
String expected = "한돈 등심으로 만든 돈까스";
// 검증
assertEquals(expected, food);
}
public class IngredientFactory {
public Ingredient get(String menu){
switch (menu){
case "돈까스":
return new Pork("한돈 등심");
case "스테이크":
return new Beef("한우 꽃등심");
case "크리스피 치킨":
return new Chicken("국내산 닭");
default:
return null;
}
}
}
public abstract class Ingredient {
private String name;
public Ingredient(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
동작에 필요한 객체를 외부에서 받아왔다.
요리 메뉴가 바뀌어도 코드 변경없이 요리 가능하다.
DI를 통한 코드 개선 효과
IngredientFactory와 Chef 클래스의 객체를 IoC Container에 등록하기
스프링 부트를 사용해서 가져오기
@SpringBootTest
class ChefTest {
@Autowired
IngredientFactory ingredientFactory;
@Autowired
Chef chef;
...
}
해당 클래스의 객체를 만들고, IoC Container에 객체를 등록해주는 어노테이션
@Component
public class IngredientFactory {
...
}
.
.
.
@Component
public class Chef {
...
}
IoC Container에서 객체를 관리해주기 때문에 아래 객체 생성 코드는 이제 필요없다.
// IngredientFactory ingredientFactory = new IngredientFactory();
// Chef chef = new Chef(ingredientFactory);
객체간 의존성이 높은 코드는 요구사항 변경에 취약하다.
따라서 외부에서 값을 삽입받는 방식인 DI 방식으로 사용하면 코드를 유연하게 작성할 수 있다.
더 나아가서 IoC Container에 필요한 객체를 등록하고 AutoWired를 통해 DI를 땡겨올 수 있었다.