개발/Spring
[Spring 입문] 3 layer architecture / IoC와 DI
서해쭈꾸미
2024. 5. 17. 01:18
3 layer architecture가 필요한 이유
Controller 클래스 하나로 모든 API를 처리한다면, 한 개의 클래스에 너무 많은 양의 코드가 존재하기 때문에 코드를 이해하기 어렵고 코드의 추가 혹은 변경 요청이 계속 생길 수 있기에 역할을 분리해줄 필요가 있다.
controller(출입문, 출구) ←→ service(사용자 요구사항 (logic) 처리) ←→ repository (DB 저장, 조회 필요할 때)
전체 흐름
Client ←(Request,Response)→ controller ←→ service ←→ repository ←→ DB
IoC와 DI
IoC(설계원칙 = 제어의 역전 ), DI(디자인 패턴 = 의존성 주입 )
의존성
강하게 결합된 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 가 치킨이 아니라 피자를 먹고 싶어 한다면? 많은 수의 코드 변경이 불가피하다.
강하게 의존되어 있는 것을 해결하기 위해선 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("피자를 먹는다.");
}
}
주입
1.필드를 통한 주입
public class Consumer {
Food food;
void eat() {
this.food.eat();
}
public static void main(String[] args) {
Consumer consumer = new Consumer();
consumer.food = new Chicken();
consumer.eat();
consumer.food = new Pizza();
consumer.eat();
}
}
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("피자를 먹는다.");
}
}
2.메서드를 통한 주입
public class Consumer {
Food food;
void eat() {
this.food.eat();
}
public void setFood(Food food) {
this.food = food;
}
public static void main(String[] args) {
Consumer consumer = new Consumer();
consumer.setFood(new Chicken());
consumer.eat();
consumer.setFood(new Pizza());
consumer.eat();
}
}
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("피자를 먹는다.");
}
}
3.생성자를 통한 주입
public class Consumer {
Food food;
public Consumer(Food food) {
this.food = food;
}
void eat() {
this.food.eat();
}
public static void main(String[] args) {
Consumer consumer = new Consumer(new Chicken());
consumer.eat();
consumer = new Consumer(new Pizza());
consumer.eat();
}
}
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로 역전된 것을 알 수 있다. 이제는 추가적인 코드변경 없이 Consumer가 어느 Food든 전부 먹을 수 있게 되었다.
즉, DI를 통해 IoC를 이루어냈다는 것을 알 수 있다.