[개발자가 반드시 정복해야 할 객체 지향과 디자인 패턴] DI(Dependency Injection)와 서비스 로케이터

·2022년 10월 31일
0
post-thumbnail
  • 소프트웨어의 두가지 영역

    • 어플리케이션 영역: 고수준 정책 및 저수준 구현을 포함하는 영역
    • 메인 영역: 어플리케이션이 동작하도록 각 객체들을 연결해주는 영역
  • 메인 영역에서 객체를 연결하기 위해 사용되는 방법

    1. DI (dependency injextion; 의존성 주입)
    2. 서비 스로케이터

[1] 어플리케이션 영역과 메인 영역

  • 메인영역의 역할
    - 어플리케이션 영역에서 사용될 객체를 생성
    - 각 객체 간의 의존 관계를 설정
    - 어플리케이션을 실행

  • 모든 의존은 메인 영역에서 어플리케이션 영역으로 향한다.
  • 어플리케이션 영역에서 메인 영역으로의 의존은 존재하지 X
  • 즉, 메인 영역을 변경한다고 해도 어플리케이션 영역은 변경되지 않는다.
public class Locator {
	private static Locator instance;
	public static Locator getInstance() {
		return instance;
	}

	public static void init(Locator locator) {
		instance = locator;
	}

	private PageValidChecker pageValidChecker;
	private ScoreCalculator scoreCalculator;
	private WinnerSelector winnerSelector;
	public Locator(PageValidChecker pageValidChecker, ScoreCalculator scoreCalculator,
		WinnerSelector winnerSelector) {
		this.pageValidChecker = pageValidChecker;
		this.scoreCalculator = scoreCalculator;
		this.winnerSelector = winnerSelector;
	}

	public PageValidChecker getPageValidChecker() { return pageValidChecker; }
	public ScoreCalculator getScoreCalculator() { return scoreCalculator; }
	public WinnerSelector getWinnerSelector() { return winnerSelector; }
}
class Main {
	public static void main(String[] args) {
		int lastPage = 400;
		PageValidChecker pageValidChecker = new PageValidCheckerImp(lastPage);
		ScoreCalculator scoreCalculator = new ScoreCalculatorImp();
		WinnerSelector winnerSelector = new WinnerSelectorImp();

		Locator locator = new Locator(pageValidChecker, scoreCalculator, winnerSelector);
		Locator.init(locator);

		final GameController gameController = new GameController();
		int answer = gameController.playGame(pobi, crong);
	}
}
import java.util.List;

public class GameController {

	public int playGame(List<Integer> pobi, List<Integer> crong) {
		PageValidChecker pageValidChecker = Locator.getInstance().getPageValidChecker();
		if (!users.stream().allMatch(pageValidChecker::checkValid)) {
			return ERROR.getCode();
		}
        // ...

		return winner.getCode();
	}
}

위 예제에서 객체들을 연결하기 위한 용도로 Locator 클래스 사용하고, Main 클래스는 이 Locator를 통해 GameController 객체가 필요로 하는 객체를 생성했다.
GameController 객체는 Locator를 이용해서 필요한 객체를 가져온 뒤에 원하는 기능 실행했다.
이렇게 사용할 객체를 제공하는 책임을 갖는 객체를 서비스 로케이터 라고 한다.

하지만 서비스 로케이터 방식보다 DI 방식을 사용하는 것이 일반적

[2] DI를 이용한 의존 객체 생성

public class GameController {
	private final PageValidChecker pageValidChecker;
	private final ScoreCalculator scoreCalculator;
	private final WinnerSelector winnerSelector;

	// 외부에서 사용할 객체를 전달받을 수 있는 방법 제공
	public GameController(PageValidChecker pageValidChecker, ScoreCalculator scoreCalculator,
		WinnerSelector winnerSelector) {
		this.pageValidChecker = pageValidChecker;
		this.scoreCalculator = scoreCalculator;
		this.winnerSelector = winnerSelector;
	}

	public int playGame(List<Integer> pobi, List<Integer> crong) {
    		int pobiScore = scoreCalculator.getScore(pobi);
    		// ...
	}
}
		
class Main {
	public static void main(String[] args) {
    	PageValidChecker pageValidChecker = new PageValidChecker();
        ScoreCalculator scoreCalculator = new ScoreCalculator();
        WinnerSelector winnerSelector = new WinnerSelector();
		final GameController gameController = new GameController(pageValidChecker, scoreCalculator, winnerSelector);
		int answer = gameController.playGame(pobi, crong);
		return answer;
	}
}

Main 클래스에서 GameController 생성자를 호출할 때, GameController 객체가 사용할 PageValidChecker, ScoreCalculator, WinnerSelector 객체를 전달한다.
즉, GameController 객체는 스스로 의존하는 객체를 찾거나 생성하지 않고, main() 메서드에서 생성자를 통해 이들이 사용할 객체를 주입한다.
=> 의존성 주입

조립기 분리

public class Assembler {
	private static GameController gameController;

	public void createAndWire() {
		PageValidChecker pageValidChecker = new PageValidCheckerImp();
		ScoreCalculator scoreCalculator = new ScoreCalculatorImp();
		WinnerSelector winnerSelector = new WinnerSelectorImp();
		this.gameController = new GameController(pageValidChecker, scoreCalculator, winnerSelector);
	}

	public GameController getGameController() {
		return this.gameController;
	}
}
class Main {
	public static void main(String[] args) {
		Assembler assembler = new Assembler();
		assembler.createAndWire();
		final GameController gameController = assembler.getGameController();
		int answer = gameController.playGame(pobi, crong);
		return answer;
	}
}

조립기를 별도로 분리하여, 변경의 유연함을 얻었다.
이는 스프링 프레임워크와 비슷한 원리이다.
스프링 프레임워크가 바로 객체를 생성하고 조립해주는 기능을 제공하는 DI 프레임워크이다.

생성자 방식과 설정 메서드 방식

  • DI 적용 방법
    1. 생성자 방식
    2. 설정 메서드 방식
// 1. 생성자 방식
public class GameController {
		private PageValidChecker pageValidChecker;
        public GameController(PageValidChecker pageValidChecker) {
        	this.pageValidChecker = pageValidChecker;
        }
        
        public void playGame() {
        	...
            this.pageValidChecker.checkValid(data);
            ...
        }
}
// 2. 설정 메서드 방식
public class GameController {
		private PageValidChecker pageValidChecker;
		private ScoreCalculator scoreCalculator;
        
        public void setPageValidChecker(PageValidChecker pageValidChecker) {
        	this.pageValidChecker = pageValidChecker;
        }
        
        public void setScoreCalculator(ScoreCalculator scoreCalculator) {
        	this.scoreCalculator = scoreCalculator;
        }
        
        
        public void playGame() {
        	...
            pageValidChecker.checkValid(data);
            scoreCalculator.getScore(data);
        }
}

설정 메서드 방식은 아래와 같이도 가능하다.

// 1. 한 개의 메서드로 의존 객체 모두 설정
public void configure(PageValidChecker pageValidChecker, ScoreCalculator scoreCalculator) {
		this.pageValidChecker = pageValidChecker;
        this.scoreCalculator = scoreCalculator;
}

// 2. 메서드 체이닝이 가능하도록 리턴 타입을 void에서 GameController로 변경
public GameController setPageValidChecker(PageValidChecker pageValidChecker) {
		this.pageValidChecker = pageValidChecker;
        return this;
}

public GameController setScoreCalculator(ScoreCalculator scoreCalculator) {
		this.scoreCalculator = scoreCalculator;
        return this;
}

0개의 댓글