๐ Chapter 9: ์ฑ๊ธํค ํจํด
๐ ๋จ์ ํ ์คํธโ
๐ ๊ฐ์ฒด ๋ฆฌํฐ๋ด๋ก ์ฑ๊ธํค ๊ณต์ ์บ์ ๊ตฌํํ๊ธฐโ
๊ฐ์ฒด ๋ฆฌํฐ๋ด์ ์๋ฐ์คํฌ๋ฆฝํธ ์ฑ๊ธํค ํจํด์ ๊ฐ์ฅ ๋จ์ํ ๊ตฌํ์ฒด๋ค. ๋ค๋ฅธ ๊ฐ์ฒด ์์ฑ ํจํด๊ณผ ๋ฌ๋ฆฌ ๋ค๋ฅธ ํจ์๋ฅผ ์์ฑํ๊ธฐ ์ํด ํธ์ถํ ํจ์๋ ์์๋ฟ๋๋ฌ new
ํค์๋๋ก ํจ์๋ฅผ ๋ ์ฐ์ด๋ผ ์ผ๋ ์๋ค.
ํ์ฌ returnValueCache
๋ ์ด๋ฏธ ๊ฐ์ฒด ๋ฆฌํฐ๋ด์ ์บ์๋ก ์ฌ์ฉํ๊ณ ์์ผ๋ฏ๋ก ๊ณต์ ์บ์๋ก ์ฌ์ฉํ ๊ฐ์ฒด ๋ฆฌํฐ๋ด์ ์ฃผ์
ํ๋ ๊ฒ ๊ฐ์ฅ ๋น ๋ฅธ ๊ธธ์ด๋ค.
๋ค์์ 8์ฅ์ ์์ ๊ธฐ๋ฐ์ผ๋ก ์์ฑํ๋ค.
describe('returnValueCache', () => {
'use strict';
var testObject;
var testValue;
var args;
var spyReference;
// ํ
์คํธ ๊ฐ์ฒด๋ฅผ ์์ฑํ๋ ๋์ฐ๋ฏธ ํจ์, testFunction์ ์คํ์ด๋ฅผ ์ฌ๊ณ
// ๋ฐํ ๊ฐ์ฒด์ spyReference ํ๋กํผํฐ์ ์คํ์ด ์ฐธ์กฐ๊ฐ์ ๋ด์๋๋ค.
function createATestObject() {
var obj = {
testFunction: function(arg) {
return testValue;
}
};
spyOn(obj, 'testFunction').and.callThrough();
// ์ ์คํฉํธ๊ฐ ์ ์ฉ๋ ์ดํ์๋
// ์คํ์ด๋ฅผ ์ง์ ์ฐธ์กฐํ ์ ์์ผ๋ฏ๋ก ํ์ฌ ์ฐธ์กฐ๊ฐ์ ๋ณด๊ดํด๋๋ค.
obj.spyReference = obj.testFunction;
return obj;
}
/*** beforeEach ์ค์ ***/
describe('advice(targetInfo)', () => {
/*** ์ด์ ํ
์คํธ ์ค์ ***/
it('์ฃผ์
๋ ์บ์๋ฅผ ์ธ์คํด์ค ๊ฐ์ ๊ณต์ ํ ์ ์๋ค', () => {
var sharedCache = {};
var object1 = createATestObject();
var object2 = createATestObject();
Aop.around('testFunction',
new Aspects.returnValueCache(sharedCache).advice,
object1,
);
Aop.around('testFunction',
new Aspects.returnValueCache(sharedCache).advice,
object2,
);
object1.testFunction(args);
// object2์ testFunction ํธ์ถ ์
// ์บ์๋ object1์ testFunction ํธ์ถ ๊ฒฐ๊ณผ๋ฅผ ๊ฐ์ ธ์จ๋ค.
expect(object2.testFunction(args)).toBe(testValue);
// ๋ฐ๋ผ์ object2์ testFunction์ ์คํ๋์ง ์๋๋ค.
expect(object2.spyReference.calls.count()).toBe(0);
});
});
});
๊ฐ์ฒด ๋ฆฌํฐ๋ด์ ๋ฐ๊ฒ๋ 8์ฅ์ returnValueCache
๋ฅผ ์์ ํ๋ค.
var Aspects = Aspects || {};
Aspects.returnValueCache = function(sharedCache) {
'use strict';
// ์ฃผ์ด์ง sharedCache๊ฐ ์๋ค๋ฉด ์ฌ์ฉํ๋ค.
var cache = sharedCache || {};
return {
advice: function(targetInfo) {
// ํจ์์ ๋๊ธด ์ธ์๋ฅผ ์บ์ ํค๋ก ์ด์ฉํ๋ค.
// (๊ฐ์ฒด ์ฐธ์กฐ๊ฐ ๋น๊ต๊ฐ ์๋, ๋ฌธ์์ด ๋น๊ต๋ฅผ ํ๊ธฐ ์ํด ๋ฌธ์์ด๋ก ๋ฐ๊พผ๋ค).
var cacheKey = JSON.stringify(targetInfo.args);
if (cache.hasOwnProperty(cacheKey)) {
return cache[cacheKey];
}
// ์ฅ์๋ ํจ์๋ฅผ ๊ฐ์ ธ์ ์คํํ ๋ค ๊ทธ ๋ฐํ๊ฐ์ ์บ์์ ์ ์ฅํ๋ค.
var returnValue = Aop.next(targetInfo);
cache[cacheKey] = returnValue;
return returnValue;
}
};
};
returnValueCache
๊ฐ ๊ณต์ ์บ์๋ฅผ ์ ์ํ ์ผ๋ restaurantApi.getRestaurantsWithinRadius
์์ ์ ์คํฉํธ ์ ์ฉ ๋ถ๋ถ์ ์์ ํ๋ค.
var Conference = Conference || {};
Conference.caches = Conference.caches || {};
// restaurantApi.getRestaurantsWithinRadius ํจ์์์
// ์บ์๋ก ์ฌ์ฉํ ๊ฐ์ฒด ๋ฆฌํฐ๋ด(์ฑ๊ธํค) ์์ฑ
Conference.caches.getRestaurantsWithinRadius = {};
// getRestaurantsWithinRadius์ ๋ฉ๋ชจ์ด์ ์ด์
ํจํด ์ ์ฉ
Aop.around(
'restaurantApi',
function addMemoizationToGetRestaurantsWithinRadius(targetInfo) {
// ThirdParty.restaurantApi()๊ฐ ๋ฐํํ ์๋ณธ API
var api = Aop.next.call(this, targetInfo);
// getRestaurantsWithinRadius ํจ์๋ฅผ ์ฅ์ํ์ฌ ๋ฉ๋ชจ์ด์ ์ด์
(๊ณต์ ์บ์๋ก) ์ถ๊ฐ
Aop.around('getRestaurantsWithinRadius',
Aspects.returnValueCache(Conference.caches.restaurantsWithinRadiusCache).advice,
api,
);
// ๊ณ ์น API๋ฅผ ๋ฐํํ๋ค.
return api;
},
ThirdParty,
);
๊ฐ์ฒด ๋ฆฌํฐ๋ด์ ๊ทธ์ผ๋ง๋ก ๊ฐ์ฅ ๋จ์ํ ์ฑ๊ธํค ํจํด ๊ตฌํ์ฒด์ด์ง๋ง, ๋ชจ๋ ํจํด ๋ฑ ํ ๊ฐ์ฒด ์์ฑ ํจํด์ด ์ ๊ณตํ๋ ๋ฐ์ดํฐ ๊ฐ์ถค ๋ฐ์์ ๊ธฐ๋ฅ์ ์๋ค.
๐ ๋ชจ๋๋ก ์ฑ๊ธํค ๊ณต์ ์บ์ ๊ตฌํํ๊ธฐโ
์ ์์ ์ ๊ณต์ ์บ์๋ ๊ฐ์ฒด ๋ฆฌํฐ๋ด restaurantsWithinRadiusCache
๋ฅผ Conference.caches
์ด๋ฆ๊ณต๊ฐ์ ์ ์ธํ์ฌ ๋ง๋ค์๋ค. ๋๊ฐ ๊ฐ๊ฒ ๋ฆฌํฐ๋ด ์ ๋๋ฉด ์บ์ ์ฉ๋๋ก ๋์์ง ์์๋ฐ, ๋ ๊ธฐ๋ฅ์ด ๋ค์ํ ์บ์๋ฅผ ์จ์ผ ํ ๋๋ ์๋ค. ์ด๋ฅผํ
๋ฉด ๊ฐ์ฅ ์ค๋์ ์ ์บ์ํ ๊ฐ์ ๊ฐ์ฅ ์ต๊ทผ ์บ์๊ฐ์ผ๋ก ๋์ฒดํ๋ฉด์ ํญ์ ์ผ์ ํ ๊ฐ์์ ๊ฐ๋ค๋ง ๋จ๊ธฐ๋, ์ต์ ์ฌ์ฉ๋น๋(LRU) ์บ์๊ฐ ์์ด์ผ ํ ๋๋ ์๊ณ , ์ผ์ ์๊ฐ๊น์ง๋ง ์บ์๊ฐ์ ์ ์ฅํด๋๋ ๊ฒ ๋ ํฉ๋ฆฌ์ ์ธ ์ํฉ๋ ์์ ์ ์๋ค. ์ด์จ๋ ์บ์๊ฐ ๊ฐ์ฒด ๋ฆฌํฐ๋ด์ธ ํ ์ด๋ฐ ๋ถ๊ฐ ๊ธฐ๋ฅ๊น์ง ๊ตฌํํ๊ธฐ ์ด๋ ต๋ค.
๊ทธ๋์ ํ๋กํผํฐ ์ ๊ทผ ๋ฐฉ์์ด ์๋ API๋ฅผ ํ ํด ๊ฐ์ฒด ๋ฆฌํฐ๋ด ๊ธฐ๋ฐ์ ์บ์ ๊ธฐ๋ฅ์ ์ ๊ณตํ๋ Conference.simpleCache
๋ชจ๋์ ์ง์ ๊ฐ๋ฐํ๋ค.
var Conference = Conference || {};
Conference.simpleCache = function() {
'use strict';
var privateCache = {};
function getCacheKey(key) {
return JSON.stringify(key);
}
return {
// ์บ์์ ์๋ ํค๋ฉด true, ์๋๋ฉด false๋ฅผ ๋ฐํํ๋ค.
hasKey: function(key) {
return privateCache.hasOwnProperty(getCacheKey(key));
},
// ์บ์์ ํด๋น ํค๊ฐ์ ์ ์ฅํ๋ค.
setValue: function(key, value) {
privateCache[getCacheKey(key)] = value;
},
// ํด๋น ํค๊ฐ์ ๋ฐํํ๋ค.
// (์บ์์ ํค๊ฐ์ด ์์ผ๋ฉด undefined)
getValue: function(key) {
return privateCache[getCacheKey(key)];
}
};
};
simpleCache
๋ฅผ ์ฌ์ฉํ๋ ค๋ฉด returnValueCache
๋ฅผ ์กฐ๊ธ ๊ณ ์ณ์ผ ํ๋ค. ๊ณต์ ์บ์๋ฅผ ์ฃผ์ง ์์ผ๋ฉด ๊ฐ์ฒด ๋ฆฌํฐ๋ด ๋์ ์ simpleCache
๊ฐ ์์ฑ๋๊ณ ์ดํ๋ก๋ ์บ์ ๊ฐ์ฒด ํ๋กํผํฐ๋ฅผ ์ง์ ๊ฑด๋๋ฆฌ์ง ์๊ณ simpleCache
API๊ฐ ํ์ถํ ๋ฉ์๋๋ฅผ ์ฌ์ฉํ๊ฒ ๋ ๊ฒ์ด๋ค.
์ด ์ returnValueCache
์ ๋จ์ ํ
์คํธ์์ simpleCache
๋ฅผ ์ฐ๊ธฐ ์ํด ์์ ํ ๋ถ๋ถ์ ํ ์ค ๋ฟ์ด๋ค.
describe('returnValueSimpleCache', () => {
'use strict';
/*** ์ค์ ๋ฐ ์ ํธ๋ฆฌํฐ ํจ์๋ ๊ทธ๋๋ก์ด๋ฏ๋ก ์ค์ ***/
describe('advice(targetInfo)', () => {
/*** ์ด์ ํ
์คํธ ์ค์ ***/
it('์ฃผ์
๋ ์บ์๋ฅผ ์ธ์คํด์ค ๊ฐ์ ๊ณต์ ํ ์ ์๋ค', () => {
// ๋ณ๊ฒฝ
// ๊ณต์ ์บ์ ๊ฐ์ฒด, simpleCache๋ฅผ ์์ฑํ๋ค.
var sharedCache = Conference.simpleCache();
var object1 = createATestObject();
var object2 = createATestObject();
Aop.around('testFunction',
new Aspects.returnValueCache(sharedCache).advice,
object1,
);
Aop.around('testFunction',
new Aspects.returnValueCache(sharedCache).advice,
object2,
);
object1.testFunction(args);
// object2์ testFunction ํธ์ถ ์
// ์บ์๋ object1์ testFunction ํธ์ถ ๊ฒฐ๊ณผ๋ฅผ ๊ฐ์ ธ์จ๋ค.
expect(object2.testFunction(args)).toBe(testValue);
// ๋ฐ๋ผ์ object2์ testFunction์ ์คํ๋์ง ์๋๋ค.
expect(object2.spyReference.calls.count()).toBe(0);
});
});
});
์ด๋ ๊ฒ simpleCache
๋ฅผ ์์ฑํ์ฌ returnValueCache
๊ฐ simpleCache
๋ฅผ ์ฐ๋๋ก ๊ณ ์ณค์ผ๋ ๋ค์์ restaurantApi.getRestaurantsWithinRadius
ํจ์๊ฐ ์ฌ์ฉํ ์ฑ๊ธํค ์บ์ ๊ฐ์ฒด๋ฅผ ๋ง๋ค ์ฐจ๋ก๋ค.
๊ฐ์ฒด ๋ฆฌํฐ๋ด์ ๊ณต์ ์บ์๋ก ์ธ ๋๋ ๊ฐ์ฒด ๋ฆฌํฐ๋ด์ด ์ด๋ฏธ ์ฑ๊ธํค์ด๋ ์บ์ ์ธ์คํด์ค๊ฐ ํ๋๋ง ์๋ค๋ ์ฌ์ค์ด ๋ช ๋ฐฑํ๋ค. ์ง๊ธ์ ๋ชจ๋ ์์ฒด๊ฐ ์บ์๋ผ ์ํฉ์ด ๋ค๋ฅธ๋ฐ, ๋ชจ๋ ํจ์๋ฅผ ์คํํ๋ฉด ๊ฐ์ ๊ฐ์ฒด ์ธ์คํด์ค๋ฅผ ์์ฑํ๊ธฐ ๋๋ฌธ์ด๋ค.
restaurantApi
์ธ์คํด์ค๊ฐ ๋ค๋ค ๋จ์ผ simpleCache
์ธ์คํด์ค๋ฅผ ๋ฐ๋ผ๋ณด๊ฒ ํ๋ ค๋ฉด ์ฆ์ ์คํ ๋ชจ๋์ ์์ฉํด์ ์ฑ๊ธํค ํจํด์ ๊ตฌํํ๋ ๊ฒ์ด ์ข๋ค. RestaurantsWithinRadiusCache
๋ชจ๋์ ํธ์ถํ ๋๋ง๋ค ๋๊ฐ์ simpleCache
์ธ์คํด์ค๋ฅผ ๋ฐํํ๋ ๋จ์ผ ํจ์์ธ getInstance
๋ฅผ ํ์ถํ๋ค.
describe('Conference.caches.RestaurantsWithinRadiusCache', () => {
'use strict';
describe('getInstance', () => {
it('ํญ์ ๋์ผํ ์ธ์คํด์ค๋ฅผ ๋ฐํํ๋ค', () => {
// .getInstance๊ฐ ๋์ผํ ๊ฐ์ฒด๋ฅผ ๋ฐํํ๋์ง ํ์ธ
expect(Conference.caches.RestaurantsWithinRadiusCache.getInstance())
.toBe(Conference.caches.RestaurantsWithinRadiusCache.getInstance());
});
});
});
๋ค์์ RestaurantsWithinRadiusCache
๊ตฌํ๋ถ๋ค.
var Conference = Conference || {};
Conference.caches = Conference.caches || {};
// restaurantApi.getRestaurantsWithinRadius ํจ์์์
// ์บ์๋ก ์ฌ์ฉํ simpleCache(์ฑ๊ธํค) ์์ฑ
Conference.caches.RestaurantsWithinRadiusCache = (function() {
'use strict';
var instance = null;
return {
getInstance: function() {
if(!instance) {
instance = Conference.simpleCache();
}
return instance;
}
};
})();
์ ์์ ์์ ์ฆ์ ์คํ ํจ์์ ๋ฐํ๊ฐ์ RestaurantsWithinRadiusCache
์ ํ ๋นํ๋ฉด์ ์ด ๊ฐ์ฒด๋ฅผ getInstance
ํจ์๋ฅผ ํ์ถํ ์ฑ๊ธํค ๊ฐ์ฒด๋ก ํ์คํ ์๋ฆฌ๋งค๊นํ๋ค. getInstance
๋ฅผ ๋งจ ์ฒ์ ์คํํ๋ฉด ์จ์ด ์๋ instance
๋ณ์๊ฐ simpleCache
๋ก ์ฑ์์ง๋ค. ๋์ค์ getInstance
๋ฅผ ๋ช ๋ฒ์ด๊ณ ํธ์ถํด๋ ๊ฐ์ instance
๋ฅผ ๋ด์ด์ค๋ค.
์ธ์คํด์ค ๊ฐ์ฒด์ ์ธ์คํด์คํ๋ฅผ ๋์ค์ผ๋ก ๋ฏธ๋ฃฐ ์ ์๋ค๋ ์ ์ด ์ฑ๊ธํค ํจํด์ ๋ ๋ค๋ฅธ ๋งค๋ ฅ์ด๋ค. ์ธ์คํด์ค ๊ฐ์ฒด๋ฅผ ์์ฑํ๋ ๋ฐ ์๊ฐ/๋น์ฉ์ด ๋ง์ด ๋ฃ๋ค๋ฉด ์ค์ํ ์ฅ์ ์ด๋ค.
๋ง์ง๋ง์ผ๋ก ์ ๋
ํ ์ ์ returnValueCache
์ ์คํฉํธ๋ฅผ restaurantApi.getRestaurantsWithinRadius
ํจ์์ ์ ์ฉํ ๋ถ๋ถ ์ญ์ ์๋ก์ด RestaurantsWithinRadiusCache
์ฑ๊ธํค์ ์ฐ๊ฒ๋ ๊ณ ์ณ์ผ ํ๋ค๋ ์ฌ์ค์ด๋ค.
// getRestaurantsWithinRadius์ ๋ฉ๋ชจ์ด์ ์ด์
ํจํด ์ ์ฉ
Aop.around(
'restaurantApi',
function addMemoizationToGetRestaurantsWithinRadius(targetInfo) {
// ThirdParty.restaurantApo()๊ฐ ๋ฐํํ ์๋ณธ API
var api = Aop.next.call(this, targetInfo);
// ์ฑ๊ธํค ์บ์ ์ธ์คํด์ค๋ฅผ ๊ฐ์ ธ์จ๋ค.
var cache = Conference.caches.RestaurantsWithinRadiusCache.getInstance();
// getRestaurantsWithinRadius ํจ์๋ฅผ ์ฅ์ํ์ฌ
// ๋ฉ๋ชจ์ด์ ์ด์
(๊ณต์ ์บ์๋ก) ์ถ๊ฐ
Aop.around('getRestaurantsWithinRadius',
Aspects.returnValueCache(cache).advice, api);
// ๊ณ ์น API๋ฅผ ๋ฐํํ๋ค.
return api;
},
ThirdParty,
);
๐ ์ ๋ฆฌํ๊ธฐโ
์ฑ๊ธํค์ ์๋ฐ์คํฌ๋ฆฝํธ์์ ๋๋ฆฌ ์ฐ์ด๋ ํจํด์ด๋ค. ์ ์ญ ์ด๋ฆ๊ณต๊ฐ์ ์ ํ๋ฆฌ์ผ์ด์ ์ ํจ์๋ ๋ณ์๋ก ์ค์ผ์ํค์ง ์์ ์ํ์์ ์ด๋ฆ๊ณต๊ฐ์ ์์ฑํ ๋ ์ ์ฉํ๋ค. ๋ํ, ์บ์์ฒ๋ผ ๋ชจ๋๋ผ๋ฆฌ ๋ฐ์ดํฐ๋ฅผ ๊ณต์ ํ๋ ์ฉ๋๋ก ์ต์ ์ด๋ค.
์๋ฐ์คํฌ๋ฆฝํธ๋ ์ฑ๊ธ ์ค๋ ๋๋ก ์์ง์ด๋ฏ๋ก ๋ค๋ฅธ ์ธ์ด๋ณด๋ค ๊ฐ์ฒด ๋ฆฌํฐ๋ด๊ณผ ์ฆ์ ์คํ ๋ชจ๋ ๊ฐ์ ์ฑ๊ธํค ํจํด์ ์ฌ๋ฐ๋ฅด๊ฒ ๊ตฌํํ๊ธฐ ์ฝ๋ค. ์ฌ๋ฌ ์ค๋ ๋๊ฐ ์ฑ๊ธํค ๊ฐ์ฒด์ ๋์์ ์ ๊ทผํ ๋ ์ผ๊ธฐ๋๋ ๋ฌด์ ๋ ๊ณ ๋ฏผํ์ง ์์๋ ๋๋ค.
์ฑ๊ธํค ํจํด ๊ตฌํ์ ๋ฐ๋ฅธ ๋จ์ ํ ์คํธ๋ ๊ฐ์ฒด๊ฐ ์๋์ง ํ์ธํ๋ ์ผ์ด ๊ด๊ฑด์ด๋ค.