본문 바로가기
책 내용 정리/[책] 개발자가 반드시 정복해야 할 객체지향과 디자인 패턴

[6] DI와 서비스 로케이터

by 문자메일 2023. 3. 4.

https://link.coupang.com/a/QoNwV

 

개발자가 반드시 정복해야 할 객체 지향과 디자인 패턴

COUPANG

www.coupang.com

본문의 내용은 위 책에서 정리한 내용 일부이며, 자세히 알고 싶은 분들은 위 책을 구매해서 보시는 것을 추천드립니다.

씹고 뜯고 맛보고 즐겨도 내용이 지루하지 않고, 정말 돈이 아깝지 않은 책!

 

 

 

 

로버트 C 마틴은 소프트웨어를 두 개의 영역으로 구분해서 설명하고 있는데, 한 영역은 고수준 정책 및 저수준 구현을 포함한 어플리케이션 영역이고 또 다른 영역은 어플리케이션이 동작하도록 각 객체들을 연결해주는 메인 영역이다.

메인 영역에서 객체를 연결하기 위해 사용되는 방법에는 DI (Dependency Injection), 서비스 로케이터가 있다.

 

 

메인(main) 영역

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

아래는 가장 기본형 부분의 Main 영역 코드 예시

메인 영역의 코드는 어플리케이션 영역의 객체 초기화, 의존 처리, 실행을 담당한다.

-> 생각해 봤을 때 메인이랑 흐름제어 역할 수행하는 객체는 언뜻 생각해 보았을 때 비슷한 것 같았는데, 메인 영역은 전체적인 전체 프로그램의 시작 단계에서 객체 초기화, 의존 처리, 실행을 하는 역할을 수행하고, 흐름제어 역할 수행하는 객체는 어떤 기능을 제공하기 위하여 애플리케이션 영역 단계에서 필요한 객체들을 모아서 실행 흐름을 제어하는 역할을 한다는 점이 다른 것 같음.

package chapter6;

public class Main1 {

	public static void main(String[] args) {
		// 상위 수준 모듈인 transcoder 패키지에서 사용할
		// 하위 수준 모듈 객체 생성
		JobQueue jobQueue = new FileJobQueue();
		Transcoder transcoder = new FfmpegTranscoder();
		
		// 상위 수준 모듈이 하위 수준 모듈을 사용할 수 있도록 Locator 초기화
		Locator locator = new Locator(jobQueue, transcoder);
		Locator.init(locator);
		
		// 상위 수준 모듈 객체를 생성하고 실행
		final Worker worker = new Worker();
		Thread t = new Thread(new Runnable() {
			public void run() {
				worker.run();
			}
		});
		JobCLI cli = new JobCLI();
		cli.interact();
	}
	
}

 

메인 영역은 어플리케이션 영역의 객체를 생성하고, 설정하고, 실행하는 책임을 갖기 때문에, 어플리케이션 영역에서 사용할 하위 수준의 모듈을 변경하려면 메인 영역을 수정하게 된다.

 

위 소스의 구조를 다이어그램으로 표현하면 아래와 같음.

 

 

 

DI를 이용한 의존 객체 사용

 

단계별 스탭

1. 메인 영역에서 생성자에 의존 객체를 직접 주입한다.

Worker 객체나 JobCLI 객체는 스스로 의존하는 객체를 찾거나 생성하지 않고, main() 메서드에서 생성자를 통해 저 객체들이 사용할 객체를 주입(Injection) 해준다. 

 

이렇게 누군가 외부에서 의존하는 객체를 넣어 주기 때문에, 이런 방식을 의존 주입이라고 부른다.

 

package chapter6;

public class Main2 {

	public static void main(String[] args) {
		// 상위 수준 모듈인 transcoder 패키지에서 사용할
		// 하위 수준 모듈 객체 생성
		JobQueue jobQueue = new FileJobQueue();
		Transcoder transcoder = new FfmpegTranscoder();
		
		// 상위 수준 모듈 객체를 생성하고 실행
		final Worker worker = new Worker(jobQueue, transcoder);
		Thread t = new Thread(new Runnable() {
			public void run() {
				worker.run();
			}
		});
		JobCLI cli = new JobCLI(jobQueue);
		cli.interact();
	}
	
}

 

 

DI를 통해서 의존 객체를 관리할 때에는 객체를 생성하고 각 객체들을 의존 관계에 따라 연결해 주는 조립 기능이 필요하다. 위에 코드에서는 Main 클래스가 조립기의 역할을 함께 하고 있다. 그러나 조립기를 별도로 분리하면 향후 조립기 구현 변경의 유연함을 얻을 수 있다.

 

 

package chapter6;

public class Assembler {
	
	public void createAndWire() {
		JobQueue jobQueue = new FileJobQueue();
		Transcoder transcoder = new FfmpegTranscoder();
		this.worker = new Worker(jobQueue, transcoder);
		this.jobCLI = new JobCLI(jobQueue);
	}
	
	public Worker getWorker() {
		return this.worker;
	}
	
	public JobCLI getJobCLI() {
		return this.jobCLI;
	}

}


package chapter6;

public class Main2 {

	public static void main(String[] args) {
		Assembler assembler = new Assembler();
		assembler.createAndWire();
		
		final Worker worker = assembler.getWorker();
		JobCLI jobCli = assembler.getJobCLI();
		
		Thread t = new Thread(new Runnable() {
			public void run() {
				worker.run();
			}
		});
		
		jobCli.interact();
	}
	
}

 

Assembler 클래스의 createAndWire() 메서드에서 어플리케이션 영역에서 사용할 객체를 생성하고, 생성자를 이용해서 의존 객체를 전달해 주고 있다. 그리고 메인 영역에서 실행 대상이 되는 객체를 제공하기 위한 메서드인 getWorker(), getJobCLI()를 제공하고 있다.

 

그리하여 Main 클래스에서는 Assembler 객체에게 객체 생성과 조립 책임을 위임하고, Assembler가 생성한 Worker 객체와 JobCLI 객체를 구하는 방식으로 변경된다.

-> 스프링 프레임워크가 바로 객체를 생성하고 조립해 주는 기능을 제공하는 DI 프레임워크 이다.

 

댓글