의존성 주입 (Dependency Injection)의 장점을 극대화 하기위해 의존성과 객체를 저장하는 DIContainer 를 사용합니다.

자바스크립트로 구현한 경량 DIContainer는 다음과 같습니다. (Reliable JavaScript)

DIContainer

DiContainer = function() {
	if (!(this instanceof DiContainer)) {
		return new DiContainer();
	}
	
	this.registrations = [];
};

DiContainer.prototype.messages = {
	registerRequiresArgs: '이 생성자 함수는 인자가 3개 있어야 합니다: ' +
		'문자열, 문자열 배열, 함수.'
};

DiContainer.prototype.register = function(name, dependencies, func) {
	var ix;
	if (typeof name !== 'string' ||
		!Array.isArray(dependencies) ||
		typeof func !== 'function') {
			throw new Error(this.messages.registerRequiresArgs);
	}
	for (ix=0; ix<dependencies.length; ++ix) {
		if (typeof dependencies[ix] !== 'string') {
			throw new Error(this.messages.registerRequiresArgs);
		}
	}
	this.registrations[name] = { dependencies: dependencies, func: func };
};

DiContainer.prototype.get = function(name) {
	var self = this,
		registration = this.registrations[name],
		dependencies = [];
	if (registration === undefined) {
		return undefined;
	}

	registration.dependencies.forEach(function(dependencyName) {  
		var dependency = self.get(dependencyName);
		dependencies.push( dependency === undefined ? undefined : dependency);
	});

	return registration.func.apply(undefined, dependencies);
};

컨테이너는 크게 두가지 부분으로 분리됩니다.

객체와 의존성을 등록하는 register 부분과 등록된 객체를 불러오는 get 부분으로 나뉩니다.

두 부분의 함수를 각각 DiContainer 객체의 프로토타입에 등록합니다.

DiContainer.prototype.register = function(name, dependencies, func) {
	...
}

DiContainer.prototype.get = function(name) {
	...
}

생성자 부분에서 자동으로 DiContainer 가 생성되도록 new DiContainer() 를 실행하고 등록 객체를 담을 registrations 배열을 초기화 합니다.

DiContainer = function() {
	if (!(this instanceof DiContainer)) {
		return new DiContainer();
	}
	
	this.registrations = [];
};

객체 등록 (register)

DiContainer.prototype.register = function(name, dependencies, func)

객체등록(register) 부분을 보면 에서 전달된 의존성의 객체의 이름과 의존성 의존성 객체(함수)가 정상적으로 전달되었는지 확인합니다.

	if (typeof name !== 'string' ||
		!Array.isArray(dependencies) ||
		typeof func !== 'function') {
			throw new Error(this.messages.registerRequiresArgs);
	}

정상적으로 전달되었다면 전달된 dependencies 배열로 루프를 실행하며 데이터가 정상적인 문자형 의존성명(dependency name)인지 확인합니다.

	for (ix=0; ix<dependencies.length; ++ix) {
		if (typeof dependencies[ix] !== 'string') {
			throw new Error(this.messages.registerRequiresArgs);
		}
	}

정상적이라면 전달된 의존성명을 registrations 배열의 인덱스로 활용하여 dependencies, func 를 저장합니다.

this.registrations[name] = { dependencies: dependencies, func: func }

등록 객체 가져오기 (register)

DiContainer.prototype.get = function(name)

등록된 registration.dependencies 배열로 루프를 돌며 등록된 의존성 명으로 가져온 의존성(dependency)를 dependencies 에 등록(push)합니다.

registration = this.registrations[name]
	...
registration.dependencies.forEach(function(dependencyName) {  
	// 등록된 의존성명(dependencyName)으로 객체를 가져옵니다. 
	var dependency = self.get(dependencyName);
	dependencies.push( dependency === undefined ? undefined : dependency);
});

마지막으로 요청한 객체의 의존성 까지 리턴합니다.

return registration.func.apply(undefined, dependencies);

실제 컨테이너 객체를 사용할때는

mainFunc = container.get(main); 형식으로 객체와 의존성을 불러옵니다.

DIContainer 활용하기

var container = new DiContainer();
var main = 'main',
	mainFunc,
	dep1 = 'dep1',
	dep2 = 'dep2';

container.register(dep1, [], function() {
	return function() {
		return 1;
	};
});

container.register(dep2, [], function() {
	return function() {
		return 2;
	};
});

container.register(main, [dep1, dep2], function(dep1Func, dep2Func) {
	return function() {
		return dep1Func() + dep2Func();
	};
});

mainFunc = container.get(main);
console.log(mainFunc())

main 객체는 dep1, dep2의존성을 가지고 있기에 객체 등록을 해야 합니다.

dep1 객체를 등록합니다. function() { return 1; }; 간단하게 1을 리턴하는 객체 입니다.
javascript container.register(dep1, [], function() { return function() { return 1; }; });

마찬가지로 dep2 객체를 등록합니다. function() { return 2; }; 2을 리턴하는 객체 입니다.

container.register(dep2, [], function() {
	return function() {
		return 2;
	};
});

DIContainer의 get 부분에서 등록된 컨테이너의 객체를 dependencies 담고 return 으로 등록객체(func)에 apply형식으로 의존성을 전달했기 때문에 dep1Func, dep2Func 에 위에서 등록(register)한 dep1, dep2 의존성 객체가 차례로 등록됩니다. 결과적으로 dep1, dep2로 저장된 dep1Func, dep2Func 를 실행할 수 있습니다.

container.register(main, [dep1, dep2], function(dep1Func, dep2Func) {
	return function() {
		return dep1Func() + dep2Func();
	};
});

우리가 예상한 결과가 나오게 됩니다.

var mainFunc = container.get(main);
console.log(mainFunc());

Reference

  1. Reliable JavaScript

Repo

  1. 소스