DI에 대해 알아보자!

송현진·2023년 7월 21일

Spring

목록 보기
6/10
post-thumbnail

DI(Dependency Injection)란?

의존성 주입이다.
이는 필요한 객체를 직접 생성하는 것이 아닌 외부로부터 객체를 받아서 사용하는 것이다.
이를 통해 객체간의 결합도를 줄이고 코드의 재사용성을 높일 수 있습니다.

의존성 주입 방식

  1. 생성자 주입 방법
@Service
public class UserServiceImpl implements UserService{
	private UserRepository userRepository;
    private MemberService memberService;
    @Autowired // 생략 가능
    public UserServiceImpl(UserRepository userRepository, MemberService memberService){
    	this.userRepository = userRepository;
        this.memberService = memberService;
    }
}
  • Spring에서 권장하는 의존성 주입 방법이다.
    이유는 1. 순환 참조 방지 2. 불변성 가짐 3. 테스트에 용이 하기 때문이다.

  • 컴파일 시점에서 순환 참조를 알 수 있다(다른 DI주입 방식에선 알 수 없음)

  • 생성자 주입은 호출 시점에 1회 보장(불변)

    • 의존 관계 주입은 처음 어플리케이션 실행될 때 대부분 정해지고 종료 전까지 변경되지 않아야 한다.
      만약, 변경 가능성이 있다면 실수로 변경할 수 있으며, 변경하면 안되는 메서드가 변경할 수 있게 설계되는 것은 좋은 방법이 아니다.
  • final을 사용할 수 있기 때문에 주입을 강제할 수 있고, 리플랙션 같은 공격으로 재할당도 불가능하다

  • 순수한 자바 코드로 단위 테스트를 작성하기 가장 좋다

  1. 수정자 주입 방법
@Service
public class UserServiceImpl implements UserService{
	private UserRepository userRepository;
    private MemberService memberService;
    @Autowired
    public setUserRepository(UserRepository userRepository){
    	this.userRepository = userRepository;
    }
    @Autowired
    public setMemberService(MemberService memberService){
    	this.memberService = memberService;
    }
}
  • 선택과 변경 가능성이 있는 의존 관계에 사용
    • 언제든 변경되게 할 위험이 있음
    • public으로 열어두어야 되는 단점이 있음
  • @Autowired로 주입할 대상 없는 경우(빈에 존재하지 않는 경우) 오류 발생
    주입 대상 없도록 하려면 @Autowired(required = falase) 설정 가능
  1. 필드 주입
@Service
public class UserServiceImpl implements UserService{
	@Autowired
    private UserRepository userRepository;
    @Autowired
    private MemberService memberService;
}
  • 코드가 간결해짐
  • 외부에서 변경이 불가능
  • 반드시 DI 프레임워크가 존재해야한다
  • 테스트 코드에서 Mock데이터 사용해 주입하면 안된다

의존성 주입을 예제로 한번 알아보자!

의존성 주입전(Before)

🚙 Bus

public class Bus {
    private String name;
    public Bus(String name){
        this.name = name;
    }
    public String getName() {
        return name;
    }
}

이동수단 중 버스인 Bus 클래스이다. 이름 속성을 가진다.

🧑🏻 Person

public class Person {
    public String move(String moveType){
        // 대중교통 종류
        Bus bus = new Bus("버스");

        // 이동 종류
        return bus.getName() + "로 " + moveType;
    }
}

이동하는 사람인 Person 클래스다. 목적지를 가기 위해 이동 방법을 고르는 것이다.

🏁 PersonTest

class PersonTest {
    @DisplayName("버스로 이동하기.")
    @Test
    void test(){
        // given
        Person person = new Person();
        String moveType = "타고 가다.";

        // when
        String move = person.move(moveType);

        // then
        assertThat(move).isEqualTo("버스 타고 가다.");
     }
}

이동 방법으로 생각하면 될 거 같다. 약속 장소를 가는 사람이 버스를 타고 이동했다. 이동 방법에 맞게 잘 가는 지 테스트 하는 코드이다.

🚶🏻‍♂️ Walk

public class Walk {
    private String name;
    public Walk(String name){
        this.name = name;
    }
    public String getName() {
        return name;
    }
}

하지만 다른 이동 방법이 더 좋으면 매번 클래스를 생성해 같은 코드를 작성해야 될까?? 사람이란 클래스가 아니라 이동 수단 클래스에서 원하는 이동 방법을 가져와 사용하게 된다면 보다 유연해진다. 의존 객체를 외부에서 주입(DI)해보자!

의존성 주입 후(After)

🚩 MoveWay

public class MoveWay {
    private String name;
    public MoveWay(String name){
        this.name = name;
    }
    public String getName() {
        return name;
    }
}

버스, 걷기 등 이동 방법을 관리하는 부모 클래스이다.
이동 방법들은 MoveWay 클래스를 상속받아 사용한다.

🚙 Bus

public class Bus extends MoveWay{
    public Bus(String name) {
        super(name);
    }
}

🚶🏻‍♂️ Walk

public class Walk extends MoveWay{
    public Walk(String name) {
        super(name);
    }
}

🚦 Transportation

public class Transportation {
    public MoveWay get(String moveType) {
        switch (moveType) {
            case "타고 가다":
                return new Bus("버스");
            case "걸어 가다":
                return new Walk("두 발로");
            default:
                return null;
        }
    }
}

이동 방법을 이동 수단으로 묶어 원하는 방법에 따라 이동 방법을 정한다.
이런식으로 DI를 주입해 역할을 분리하고 각 객체 간의 의존성을 줄인다.

🧑🏻 Person

public class Person {
    // 이동 수단
    private Transportation transportation;
    // 의존성 주입(DI)
    @Autowired
    public Person(Transportation transportation){
        this.transportation = transportation;
    }
    
    public String move(String moveType){
        // 이동 방법
        MoveWay moveWay = transportation.get(moveType);

        // 이동
        return moveWay.getName()+ " " + moveType;
    }
}

사람은 이동 방법을 생성하지 않고, 이동 수단에서 방법에 맞게 원하는 방법을 선택하게 했다.
역할을 분담해줘서 사람은 더욱 쉽게 방법을 택할 수 있게 되었다.

🏁 PersonTest

class PersonTest {
    @DisplayName("버스로 이동하기.")
    @Test
    void busMove(){
        // given
        Transportation transportation = new Transportation();
        Person person = new Person(transportation);
        String moveType = "타고 가다";

        // when
        String move = person.move(moveType);

        // then
        assertThat(move).isEqualTo("버스 타고 가다");
     }
    @DisplayName("두 발로 이동하기")
    @Test
    void walkMove(){
        // given
        Transportation transportation = new Transportation();
        Person person = new Person(transportation);
        String moveType = "걸어 가다";

        // when
        String move = person.move(moveType);

        // then
        assertThat(move).isEqualTo("두 발로 걸어 가다");
    }
}

의존성 주입(DI)을 통해 객체 간의 결합을 낮춰 더욱 유연하고 객체지향적은 코드를 만들어줄 수 있다.
이동 방법을 코드 변경 없이 만들어질 수 있게 코드가 개선되었다.

참고
의존성 주입 자료
의존성 주입 예제

profile
개발자가 되고 싶은 취준생

1개의 댓글

comment-user-thumbnail
2023년 7월 21일

의존성 주입에 대한 설명이 정말 상세하고 이해하기 쉽네요. 특히, 코드 예시를 통해 실제로 어떻게 적용될 수 있는지 보여주신 부분이 좋았습니다. 이런 글을 공유해주셔서 감사합니다!

답글 달기