๐ Chapter 7: ๋ถ๋ถ ์ ์ฉ ํจ์
๐ ๋จ์ ํ ์คํธโ
๊ณง ์ด๋ฆด ์๋ฐ์คํฌ๋ฆฝํธ ์ฝํผ๋ฐ์ค ํ์ฌ์ฅ์ ์์ฒ ๋ช ์ ๊ตถ์ฃผ๋ฆฐ ๊ฐ๋ฐ์๋ก ๊ฐ๋ ์ฐฐ ์ ๋ง์ด๋ค. ์ฝํผ๋ฐ์ค๊ฐ ์ด๋ฆฌ๋ฉด ํ์ฌ์ฅ์ ๋ถ์ํต์ ์ด๋ฃฐ ํ ๊ณ ์ฐธ๊ฐ์๋ค์ ์์ฌ ๊ฑฐ๋ฆด ์ฐพ์๋ค๋ ์๊ฐ์กฐ์ฐจ ์๊น์ฐ๋ ๊ฝค ์ค์ํ ๋ฌธ์ ๋ค.
์คํ์ ์ฐ์ ํ์ฌ ์ฅ ๊ทผ์ฒ์ ์๋ ์์์ ์์น๋ฅผ ์๋ ค์ฃผ๋ ์๋ํํฐ ์น ์๋น๋ฅด๋ฅผ ์ฐพ์๋ณด๊ณ , ์ฌ๋ฟ์ ์ด ์๋น์ค๋ฅผ ์ด์ฉํด์ UI๋ฅผ ๊ฐ๋ฐํ๊ธฐ๋ก ์ ๋ฌด๋ฅผ ๋ถ๋ดํ๋ค. ์นํ์ ์๋ํํฐ API ์ฝ๋๋ฅผ ๋ฐ๋ก ์ฐพ์๋ค.
var ThirdParty = ThirdParty || {};
ThirdParty.restaurantApi = function() {
'use strict';
return {
// ์ฃผ์ด์ง ์ฃผ์(address) ๊ธฐ์ค ๋ฐ๊ฒฝ radiusMiles ๋ง์ผ ์ด๋ด์ ์์นํ
// ์ํ๋ ์๋ฆฌ(cuisine)๋ฅผ ๋จน์ ์ ์๋ ์์์ ๋ฐฐ์ด์ ๋ฐํํ๋ ํ๋ผ๋ฏธ์ค๋ฅผ ๋ฐํ
getRestaurantsWithinRadius: function(address, radiusMiles, cuisine) {
// ํ๋ผ๋ฏธ์ค๋ ๋ค์๊ณผ ๊ฐ์ ๊ฐ์ฒด์ ๋ฐฐ์ด๋ก ๊ท๊ฒฐ๋๋ค.
// {
// name: '๋์ฑ๊ฐ',
// address: '์ธ์ฐ ๋จ๊ตฌ ์ ์ ๋ก 20๋ฒ๊ธธ 988',
// }
}
};
};
๊ด์ฐฎ์ API์ธ ๊ฒ ๊ฐ์๋ฐ, ํ์ ์ด์ ์ก๋คํ ๋ถ๋ถ๋ ์๋ค. ๋จผ์ , address
์ธ์๋ ์ฝํผ๋ฐ์ค ํ์ฌ์ฅ ์ฃผ์๋ผ์ ๊ฐ์ด ๋ฐ๋ ์ผ์ด ์๊ณ , ์๊ฑด์ ๊ทผ์ฒ ์์์ ์ ํ์ฌ์ฅ ๊ธฐ์ค 3km ์ด๋ด์ ์๋ ์์์ ์ด๋ฏ๋ก radiusMiles
ํ๋ผ๋ฏธ์คํฐ๊ฐ ๋ํ ์ผ์ ํ๋ค.
์นํ์ getRestaurantsNearConference(cuisine)
ํจ์๋ก API๋ฅผ ํ์ฅํ๊ธฐ๋ก ํ๋ค. address
์ radius
ํ๋ผ๋ฏธํฐ๋ฅผ ๊ณ ์ ํ ์ฑ getRestaurantsNearConference(cuisine)
๊ฐ getRestaurantsWithinRadius(address, radius, cuisine)
๋ฐํ๊ฐ์ ๋ฌด์กฐ๊ฑด ๋ฐํํ๊ฒ ํ๋ ๊ฑฐ๋ค. ์ผ๋จ ์ด๋ ๊ฒ ํด์ ๋๋ต ๋จ์ํ
์คํธ๋ฅผ ๊ทธ๋ ค๋ณธ๋ค.
describe('ThirdParty.restaurantApi() ์ ์คํฉํธ', () => {
var api = ThirdParty.restaurantApi();
describe('getRestaurantsNearConference(cuisine)', () => {
var returnFromUnderlyingFunction = '์๋ฌด๊ฑฐ';
var cuisine = '์คํ์๋ฆฌ';
beforeEach(() => {
spyOn(api, 'getRestaurantsWithinRadius')
.and.returnValue(returnFromUnderlyingFunction);
});
it('์ฌ๋ฐ๋ฅธ ์ธ์๋ก getRestaurantsWithinRadius๋ฅผ ํธ์ถํ๋ค', () => {
api.getRestaurantsNearConference(cuisine);
expect(api.getRestaurantsWithinRadius).toHaveBeenCalledWith(
'์ธ์ฐ ๋จ๊ตฌ ์ ์ ๋ก 20๋ฒ๊ธธ 988', 2.0, cuisine,
);
});
it('getRestaurantsWithinRadius๋ก๋ถํฐ ๋ฐ์ ๊ฐ์ ๋ฐํํ๋ค', () => {
var ret = api.getRestaurantsNearConference(cuisine);
expect(ret).toBe(returnFromUnderlyingFunction);
});
});
});
๐ ๋ถ๋ถ ์ ์ฉ ํจ์ ๋ง๋ค๊ธฐโ
๋ณธํจ์๋ฅผ ๊ฐ์ธ์ ์ผ๋ถ ํ๋ผ๋ฏธํฐ ๊ฐ์ ๊ณ ์ ํ๋, ๋ค์ ํจ์๋ฅผ API์ ์ถ๊ฐํ๊ณ ์ถ๋ค.
function getRestaurantsNearConference(cuisine) {
return api.getRestaurantsWithinRadius(
'์ธ์ฐ ๋จ๊ตฌ ์ ์ ๋ก 20๋ฒ๊ธธ 988', 2.0, cuisine,
);
}
์นํ์ ๋ค์๊ณผ ๊ฐ์ด ์ฝ๋ฉํ๋ค.
// ThirdParty.restaurantApi()์ getRestaurantsNearConference ๋ฉค๋ฒ๋ฅผ ์ถ๊ฐํ๋ค.
Aop.around(
// ๋ฐํ๊ฐ์ ์์ ํด์ผ ํ ํจ์
'restaurantApi',
// ๋ฐํ๊ฐ์ ์์ ํ๋ ํจ์
function addGetRestaurantNearConference(targetInfo) {
'use strict';
// ThirdParty.restaurantApi()๊ฐ ๋ฐํํ ์๋ณธ API
var api = Aop.next.call(this, targetInfo);
// API์ ์ถ๊ฐํ ํจ์
function getRestaurantsNearConference(cuisine) {
return api.getRestaurantsWithinRadius(
'์ธ์ฐ ๋จ๊ตฌ ์ ์ ๋ก 20๋ฒ๊ธธ 988', 2.0, cuisine,
);
}
// ์์ผ๋ฉด ์ด ํจ์๋ฅผ ์ถ๊ฐํ๋ค.
api.getRestaurantsNearConference =
api.getRestaurantsNearConference || getRestaurantsNearConference;
// ์์ ํ API๋ฅผ ๋ฐํํ๋ค.
return api;
},
// ๋ฐํ๊ฐ์ ์์ ํด์ผ ํ ํจ์์ ์ด๋ฆ๊ณต๊ฐ
ThirdParty
);
๐ ๋ถ๋ถ ์ ์ฉ ํจ์์ ์ปค๋ง ๊ตฌ๋ณํ๊ธฐโ
๐ ์ปค๋งโ
์ปค๋ง์ ์ธ์๋ฅผ ์ฌ๋ฟ ์ทจํ๋ ํจ์๋ฅผ ์ธ์ ํ๋๋ง ๋ฐ์ ํจ์ ์ฌ๋ฌ ๊ฐ๋ก ํด์ฒดํ๋ ๊ธฐ๋ฒ์ด๋ค. ์ฆ, ๋ค์ ์ฝ๋๋ฅผ getRestaurantsWithinRadius(address, radius, cuisine)
์ getRestaurantsCurried(address)(radius)(cuisine)
๊ณผ ๊ฐ์ด ์ฐ๋ ๊ฑธ ๋งํ๋ค.
์ฒซ ๋ฒ์งธ ํธ์ถ๋ถ getRestaurantsCurried(address)
์คํ์ด ๋๋๋ฉด radius
๋ฅผ ์ธ์๋ก ๋ฐ์ ๋ ๋ค๋ฅธ ํจ์, ์ฆ cuisine
์ ์ธ์๋ก ๋ฐ๋ ํจ์๋ฅผ ๋ฐํํ๋ ํจ์๊ฐ ๋ฐํํ๋ค. ๊ฐ์ฅ ๊น์ ์ค์ฒฉ ๋จ๊ณ์ ํจ์๊ฐ ๋ง์ง๋ง์ผ๋ก ๋ต์ ๋ด์ด์ฃผ๋ ๊ตฌ์กฐ๋ค.
function getRestaurantsCurried(address) {
var self = this;
return function(radius) {
return function(cuisine) {
return self.getRestaurantsWithinRadius(address, radius, cuisine);
}
}
}
ํ์ค์ผ(Haskell)๊ณผ ML์ฒ๋ผ ์ปค๋ง ํจ์๊ฐ ์ผ์์ ์ธ ํ๋ก๊ทธ๋๋ฐ ์ธ์ด๋ ๋ชจ๋ ํจ์๊ฐ ํ๋์ ์ธ์๋ง ๋ฐ๋๋ค. ๊ทธ๋ฐ์ ๋๋ค ๋์ํ(lambda calculus) ์ดํ ๋ฑ์์ ํจํด๊ณผ ๊ธฐ๋ฒ์ด ์๋ค.
๐ ๋ถ๋ถ ์ ์ฉ ํจ์โ
๋ถ๋ถ ์ ์ฉ ํจ์๋ ์ธ๋ป ์ธ์๋ฅผ ์ฌ๋ฟ ๋ฐ๋ ํจ์๋ฅผ ๋ ์ ์ ์ธ์๋ฅผ ๋ฐ๋ ํจ์๋ก ๋ฐ๊พธ๋ ์ปค๋ง๊ณผ ๋น์ทํด ๋ณด์ธ๋ค. ๊ทธ๋ฌ๋ ๋ค์ getRestaurantsNearConference
๊ตฌํ๋ถ๋ฅผ ๋ณด๋ฉด ์ ์ ์๋ฏ์ด ์คํ๋ ค ์ ๋ฐ๋์ ๊ฐ๊น๋ค. ์ฆ, ๋ถ๋ถ ์ ์ฉ ํจ์๋ ์ด์ ๋จ๊ณ์์ ์์ฑ๋ ์ปค๋ง ์์์ ๋ญ๊ฐ ๋ ๋ณดํ์ ๊ฒฐ๊ตญ ์ด์ฅ ์๋ถ๋ถ์์ ์๊ฐํ๋ ๋ถ๋ถ ์ ์ฉ ํจ์ ๋น์ ๊ณผ ๊ธฐ๋ฅ์ด ๊ฐ์ ํจ์๋ก ๋ง๋ ๊ฒ์ด๋ค.
function getRestaurantsNearConference(cuisine) {
return getRestaurantsCurried('์ธ์ฐ ๋จ๊ตฌ ์ ์ ๋ก 20๋ฒ๊ธธ 988')(2.0)(cuisine);
}
๐ ์ ๋ฆฌํ๊ธฐโ
๊ฐ์ด ๋ถ๋ณ์ธ ์์ ์ธ์๋ฅผ ์ง๋ ํจ์ ํธ์ถ๋ถ๋ ์์์ฑ์ ์บก์ํํ์ฌ ํจ์๋ฅผ ์๋ก ๋ง๋๋ ๊ฒ ์ข๋ค. ์ด๊ฒ์ด ๋ฐ๋ก ๋ถ๋ถ ์ ์ฉ ํจ์ ๊ธฐ๋ฒ์ด๋ค.
์๋ํ ๋๋ก ์์๋ฅผ ์ฌ์ฉํ๋์ง ์ ํํ๊ณ DRYํ ๋จ์ ํ
์คํธ๋ก ํ์ธํ๋ผ. ๋ฐํ ํ์
์ ๋ฌผ๋ก , ๋ณธํจ์์ ๋ํ ์ด๋ค ๊ฐ์ ์ด๋ ์ถ์ ๋ ํ ํ์๊ฐ ์๋ค.(๊ทธ๋ ๊ฒ ํด์๋ ์ ๋๋ค.)
๋ถ๋ถ ์ ์ฉ ํจ์๋ ์ปค๋ง๊ณผ ํผ๋ํ๊ธฐ ์ฝ๋ค. ์ง์ง ์ปค๋ง์ ์ธ์๋ฅผ ๋ถ๋ถ์ ์ผ๋ก, ์๋๋ฉด ๋ค๋ฅธ ๋ฐฉ๋ฒ์ผ๋ก ์ ์ฉํ๋ ์ผ์ด ์๋ค. ์ธ๋ ค ์ฌ๋ฌ ์ธ์๋ฅผ ๊ฑฐ๋๋ฆฐ ํจ์๋ฅผ ์ธ์๋ฅผ ํ๋๋ง ์ทจํ๋ ์ฌ๋ฌ ๋จ๊ป์ ํจ์๋ค๋ก ์ชผ๊ฐ ๋ค. ๋ถ๋ถ ์ ์ฉ ํจ์๋ก ์ธ์๋ฅผ ๋ํ์ดํ์ง ์๋ ์๋ฆฌ๊ฐ ๋ง์์ ๋ ๋ค๋ฉด ํจ์ ๋ณธ์ฒด๋ฅผ ์คํํ๋ ๋ฉ๋ชจ์ด์ ์ด์
(memoization)๊ธฐ๋ฒ ์ญ์ ํก์กฑํ ๊ฒ์ด๋ค.