의존성 주입 DI, 생성자 주입, 필드 초기화, DIP 등 비슷하지만 다른 용어들을 1주차 PR 리뷰에서 볼 수 있었다
이거겠거니 하지말고🚫🚫 정확히 알고 가보자!
의존성이 생기면 결합도가 높아진다. 이런 말을 한 번쯤 들어봤을 것이다.
무슨 의미일까?
현재 이 GameController
의 start() 메소드가 실행되려면 Game
객체가 필요하다.
public class GameController {
private Game baseballGame = new Game();
private void start() {
do {
baseballGame = initGame();
playGame(baseballGame);
} while (!baseballGame.isEnd());
}
}
이 때, 우리는 'GameController
객체는 Game
객체에 의존성을 갖는다'라고 이야기한다!
그런데 위 코드가 강한 의존관계, 결합도가 높은 코드라고?
그렇다. 의존성 주입을 하지 않고 GameController
클래스 내에서 Game
객체를 직접 생성했기 때문이다!
의존성 주입이 무엇인지는 뒤에서 알아보고,
일단 이 코드가 발생시키는 문제점을 먼저 알아보려고 한다.
Game
객체 수정 시, GameController
클래스도 함께 수정해야 한다.GameController
클래스 내에 Game
객체를 직접 생성하는 부분이 있는데,
Game baseballGame = new Game()
-----
Game baseballGame = Game.init()
만약 Game
객체 생성 방법이 이런식으로 변경된다면 GameController
클래스도 이렇게 함께 수정해야 하는 상황이 발생한다.
GameController
클래스를 재활용하기 어려워진다.GameController
클래스를 다른 상황에서 재사용하려고 할 때,
Game
클래스를 함께 가져와야하기 때문에 재활용이 어려워지는 문제가 발생한다.
다른 모듈에 대해 자세히 알수록 결합도가 높다고 할 수 있는데,
지금 GameController
는 Game
객체 생성 방법까지 알고있는 상태 = 결합도가 높다
고 할 수 있다!
결합도가 높다
= 다른 모듈에 대해 많이 안다
= 다른 모듈에 대해 많이 관여하고 있다
= 변경 사항을 수용하기 어렵다
클래스 내의 메소드가 서로 비슷한 작업을 수행하는지, 메소드들이 서로 관련이 깊은지를 말하는 개념이다.
응집도가 높다
= 클래스 내의 메소드가 서로 관련되고 비슷한 작업을 수행한다.
= 코드의 가독성과 유지 보수 용이성이 높아진다.
= 코드의 재사용성⬆️, 유지보수 용이성 ⬆️
= 객체지향적인 설계!
자 그러면 이런 객체간의 의존성을 줄이는 방법은 무엇일까?
그게 바로 의존성 주입
이다!
이렇게 GameController
의 외부에서 객체를 생성하고 가져와(주입
) 사용하는 방법을 말한다!
1. 단위 테스트가 쉬워진다.
: 내부에서 생성하는 객체는 테스트를 위해 특정 값을 주입해줄 수 없기 때문에!
2. 코드의 재활용성이 높아진다.
: GameController
클래스를 다른 상황에서 재사용하려고 할 때, Game을 의존하지 않기 때문에 재사용이 쉬워진다.
3. 객체간 의존성, 결합도가 낮아진다.
그런데 의존성을 주입하는 방법은 사실 4가지이다.
생성자 주입
수정자(setter) 주입
필드 주입
일반 메서드 주입
위의 방법은 그 중 하나인 생성자 주입 방식이었던 것이다!
단, 의존성 주입의 4가지 방법 등에 있어서 스프링에 관련된 결과가 주가 되어 나오는 것으로 보아
이 다음부터 설명하는 내용에 대해서는 순수 자바 프로그래밍과 완벽히 일치하지 않는 부분이 있을 수 있다!
public class GameController {
private Game baseballGame;
public GameController(Game baseballGame) {
this.baseballGame = baseballGame;
}
}
장점
- 객체가 항상 일관된 상태를 유지한다.
: 객체가 생성될 때 필요한 모든 의존성이 생성자를 통해 주입되기 때문에,
: 객체가 생성된 이후에는 의존성이 변경되지 않는다.- 의존성 주입을 명시적으로 나타내므로 코드의 가독성이 높아진다.
- 단위 테스트에 용이하다.
: 필요한 의존성을 생성해서 주입해주면 되기 때문에!단점
수정자(setter) 주입과 달리 객체 생성 시 의존성을 주입하므로 선택적으로 의존성을 주입할 수는 없다.
public class GameController {
private Game baseballGame;
public void setGame(Game baseballGame) {
this.baseballGame = baseballGame;
}
}
장점
선택적으로 의존성을 주입할 수 있다.
단점
생성자 주입과 달리 해당 메소드가 실행되어야 의존성이 주입되기 때문에 객체의 일관성을 보장하기 어렵다.
⇒ 의존성이 누락되어 NullPointerException과 같은 예외가 발생할 수 있다.
public class GameController {
private Game baseballGame = new Game();
private void start() {
playGame(baseballGame);
}
}
장점
코드가 간결하다.
단점
- GameController 생성자 호출 후에 Game 객체에 의존성이 주입되기 때문에 객체 상태가 변경될 수 있다.
- 테스트 하기가 어렵다.
: 객체 내부에서 의존성이 주입되기 때문에- 의존성 주입이 명시적이지 않고 숨어있어 코드의 가독성이 떨어진다.
잠깐, 필드 주입은 의존성 주입의 한 방법이지만 의존성 주입의 원칙을 따르지 않는다?
이게 무슨 홍철없는 홍철팀 같은 말인가...! 일단 내가 이해한 바로는,
1. 필드 주입은 의존성 주입의 한 형태이지만
2. 위의 경우Game
객체가GameController
내부에서 필드를 통해 직접 초기화 된다.
3. 즉, 외부에서 의존성을 주입한 것이 아니므로
3. 의존성 주입의 원칙을 따르는 방식이라고 보기 어렵다.
public class GameController {
private Game baseballGame;
private void start(Game baseballGame) {
this.baseballGame = baseballGame;
}
}
장점
객체 생성 후에 의존성을 주입해야하는 경우 사용할 수 있다.
단점
필드 주입과 마찬가지로 불변성과 객체 일관성을 위반할 수 있게된다.
비슷한 용어로 사용되고 있고, 초점의 차이가 있는 것 같다.
생성자 주입
객체가 생성될 때 필요한 의존성을 생성자를 통해 주입하는 방식
생성자 초기화
객체가 생성될 때 필요한 값 또는 상태를 설정하거나 초기화하는 것
그런데 이 생성자 주입으로 의존성 주입을 할 때,
Game
인터페이스와 이를 구현한 BaseballGame
객체가 있다면 둘 중 뭐를 주입하는 게 좋을까?
지금까지의 코드에서 변수명은 baseballGame
인데 객체명이 Game
것을 발견한 분도 있을 것이다.
그 이유가 바로 DIP, 즉, 의존 역전 원칙에 있다!
고수준 모듈이 저수준 모듈에 직접 의존하는 것을 피해야 한다는 원칙이다.
추상화에 의존해야지, 구체화에 의존하면 안 된다 - 출처: 위키백과
고수준 모듈? : 변경이 없는 추상화된 클래스 (또는 인터페이스)
저수준 모듈? : 위의 추상화된 클래스나 인터페이스를 상속받은 구현체 클래스, 변하기 쉽다.
코드로 설명해보자면
public class BaseballGame implements Game {
public BaseballGame init() {
return new BaseballGame();
}
}
------
public interface Game {
BaseballGame init(Balls answerBalls);
}
이렇게 Game
이라는 인터페이스(고수준 모듈)와 BaseballGame
이라는 구현 클래스(저수준 모듈)이 있을 때,
public class GameController {
private Game baseballGame;
public GameController(Game baseballGame) {
this.baseballGame = baseballGame;
}
}
GameController
는 의존성을 주입할 때,
변하기 쉬운 저수준 모듈인 BaseballGame
에 의존하지 말고
고수준 모듈인 인터페이스 Game
에 의존해야한다는 의미이다!
의존성 주입
필요한 객체를 객체 내에서 직접 생성하는 것이 아니라 외부에서 객체를 생성해 받아서 사용하는 것
생성자 주입
객체가 생성될 때 생성자를 통해 의존성을 주입하는 방법
리뷰가 도움이 되어서 정말 행복하네요!
앞으로 더 공부해서 저도 현지님의 블로그 처럼 지식을 더 많이 공유할 수 있는 사람이 되어야 겠다는 동기부여 얻고 갑니다!! 🥰🥰🥰