230628_ IoC와 DI

hoy_·2023년 6월 28일
0

Today, I Learned

목록 보기
27/36

1. 오늘의 일정

  1. 개인 과제, spring 숙련




2. 배운 내용

1. Ioc(Inversion of Control)

흔히 제어의 역전이라 불리는 것으로 메서드나 객체의 호출작업을 개발자가 결정하지 않고 외부에서 결정되는 것을 의미한다.

public class A {
	private B b;
    
    public A() {
    b = new B();
    }
}

기존에는 위와 같이 A는 B에 의존하고 있다는 것을 개발자가 명시해주어야 했다.

기존 객체 실행 단계
1. 객체 생성
2. 클래스 내부에서 의존성 객체 생성
3. 의존성 객체 메서드 호출


스프링에선 B 객체가 스프링 컨테이너에서 관리하는 Bean일 경우 @autowired애너테이션으로 주입받을 수 있다.

public class A {

	@Autowired
    private B b;
}

스프링이 모든 의존성 객체를 스프링이 실행될때 만들고 필요한곳에 주입시켜줌으로써 Bean들은 싱글톤 패턴의 특징을 가진다.

제어의 흐름을 사용자가 컨트롤 하는 것이 아니라 스프링에게 맡겨 작업을 처리하게 되고, 이를 IoC 개념이라고 한다.

스프링의 객체 실행 단계
1. 객체 생성
2. 제어권을 가진 스프링이 의존성 객체를 만들어 주입
3. 의존성 객체 메서드 호출


당연히 이런 개념을 아무런 이유 없이 사용하진 않을 것이다.
IoC를 사용함으로써 얻는 장점은 아래와 같다.

  • 객체 간 결합도를 낮춘다.
  • 유연한 코드 작성이 가능하다.
  • 가독성이 올라간다.
  • 코드의 중복을 방지한다.
  • 유지보수에 용이하다.



2. DI(Dependency Injection)

스프링이 다른 프레임워크와 차별화되어 제공하는 의존 관계 주입 기능으로, 객체를 직접 생성하는 게 아니라 외부에서 생성한 후 주입 시켜주는 방식이다.

DI 패턴을 사용하여 IoC 설계 원칙을 구현하는 것으로 이해하면 된다.


<주입 예제>

필드를 통한 주입

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("피자를 먹는다.");
    }
}

Food를 Consumer에 포함 시키고 Food에 필요한 객체를 주입받아 사용한다.

setter를 통한 주입

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("피자를 먹는다.");
    }
}

set 메서드를 사용하여 필요한 객체를 주입받아 사용한다.

생성자 통한 주입(추천)

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("피자를 먹는다.");
    }
}

생성자를 사용하여 필요한 객체를 주입받아 사용한다.



생성자를 통한 주입을 추천하는 이유

  • NullPointerException의 발생을 막는다.
    • 객체가 생성되는 시점에 Bean을 주입하기 때문에 의존성 없이는 생성이 불가하다.
  • final 선언으로 불변성을 유지할 수 있다.



profile
배우는 사람

0개의 댓글