๋ณธ๋ฌธ์œผ๋กœ ๊ฑด๋„ˆ๋›ฐ๊ธฐ

๐ŸŒˆ Chapter 5: ์ฝœ๋ฐฑ ํŒจํ„ด

๐Ÿ“š ๋‹จ์œ„ ํ…Œ์ŠคํŠธโ€‹

๐ŸŽˆ ์ฝœ๋ฐฑ ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•œ ์ฝ”๋“œ์˜ ์ž‘์„ฑ๊ณผ ํ…Œ์ŠคํŒ…โ€‹

์š”๊ตฌ์‚ฌํ•ญ: ์Šนํ˜„์€ ๊ณง ๊ฐœ์ตœ๋  ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ ์ฝ˜ํผ๋Ÿฐ์Šค ์›น ์‚ฌ์ดํŠธ์˜ ๋‹ด๋‹น ๊ฐœ๋ฐœ์ž๋‹ค. ๊ทธ๋Š” ์ฝ˜ํผ๋Ÿฐ์Šค ์ž์›๋ด‰์‚ฌ์ž๊ฐ€ ์‚ฌ์šฉํ•  ์ฐธ๊ฐ€์ž ์ฒดํฌ์ธ ํ™”๋ฉด์„ ๊ตฌํ˜„ํ•ด์•ผ ํ•œ๋‹ค. ์ด ์‹ ๊ทœ ํ™”๋ฉด์€ ์ฐธ๊ฐ€์ž ๋ชฉ๋ก์„ ๋ณด์—ฌ์ฃผ๊ณ  ๊ทธ์ค‘ ํ•œ ์‚ฌ๋žŒ ๋˜๋Š” ์—ฌ๋Ÿฌ ์‚ฌ๋žŒ์„ ์„ ํƒ(์ฒดํฌ์นญํ•œ ๊ฒƒ์œผ๋กœ ํ‘œ์‹œ) ํ•œ ๋’ค ์™ธ๋ถ€ ์‹œ์Šคํ…œ๊ณผ ์—ฐ๋™ํ•˜์—ฌ ์ฒดํฌ์ธ์„ ์™„๋ฃŒํ•œ๋‹ค. UI ๋ฐฐํ›„์˜ ์ฒดํฌ์ธ ๊ธฐ๋Šฅ์€ checkInService ํ•จ์ˆ˜๋กœ ๊ตฌํ˜„ํ•˜๊ธฐ๋กœ ํ–ˆ๋‹ค.

์ฐธ๊ฐ€์ž๋ฅผ ๊ฐ๊ฐ ์ฒดํฌ์ธํ•˜๋ ค๋ฉด attendeeCollection ๊ฐ์ฒด๋Š” ์ฐธ๊ฐ€์ž ๊ฐœ์ธ๋ณ„๋กœ ์–ด๋–ค ์•ก์…˜์„ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ๋Š” ๊ตฌ์กฐ์—ฌ์•ผ ํ•œ๋‹ค. ์Šนํ˜„์€ ๋ฐ”๋กœ ์ด ์•ก์…˜์„ ์ฝœ๋ฐฑ ํ•จ์ˆ˜์— ๋„ฃ์–ด ์‹คํ–‰ํ•˜๊ณ  ์‹ถ๋‹ค. ๋จผ์ € contains, add, remove ํ•จ์ˆ˜๊ฐ€ ํ•„์ˆ˜์ธ attendeeCollection์„ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ •์˜ํ•œ๋‹ค.

var Conference = Conference || {};
Conference.attendeeCollection = function() {
var attendees = [];

return {
contains: function(attendee) {
return attendees.indexOf(attendee) > -1;
},
add: function(attendee) {
if (!this.contains(attendee)) {
attendees.push(attendee);
}
},
remove: function(attendee) {
var index = attendees.indexOf(attendee);

if (index > -1) {
attendees.splice(index, 1);
}
},

iterate: function(callback) {
// attendees์˜ ๊ฐ attendee์— ๋Œ€ํ•ด ์ฝœ๋ฐฑ์„ ์‹คํ–‰ํ•œ๋‹ค.
}
}
}

iterate๋ฅผ ๊ตฌํ˜„ํ•˜๊ธฐ ์ „์— ์ผ๋‹จ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ๋ฅผ ๋งŒ๋“ค์–ด ๊ธฐ๋Šฅ์„ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ ๊ฒ€ํ•  ์ˆ˜ ์žˆ๋‹ค.

describe('Conference.attendeeCollection', () => {
describe('contains(attendee)', () => {
// contains ํ…Œ์ŠคํŠธ
});

describe('add(attendee)', () => {
// add ํ…Œ์ŠคํŠธ
});

describe('remove(attendee)', () => {
// remove ํ…Œ์ŠคํŠธ
});

describe('iterate(callback)', () => {
var collection, callbackSpy;

// ๋„์šฐ๋ฏธ ํ•จ์ˆ˜
function addAttendeesToCollection(attendeeArray) {
attendeeArray.forEach(function(attendee) {
collection.add(attendee);
});
}

function verifyCallbackWasExecutedForEachAttendee(attendeeArray) {
// ๊ฐ ์›์†Œ๋งˆ๋‹ค ํ•œ ๋ฒˆ์”ฉ ์ŠคํŒŒ์ด๊ฐ€ ํ˜ธ์ถœ๋˜์—ˆ๋Š”์ง€ ํ™•์ธํ•œ๋‹ค.

expect(callbackSpy.calls.count()).toBe(attendeeArray.length);

// ๊ฐ ํ˜ธ์ถœ๋งˆ๋‹ค spy์— ์ „๋‹ฌํ•œ ์ฒซ ๋ฒˆ์งธ ์ธ์ž๊ฐ€ ํ•ด๋‹น attendee์ธ์ง€ ํ™•์ธํ•œ๋‹ค.
var allCalls = callbackSpy.calls.all();
for (var i = 0; i < allCalls.length; i++) {
expect(allCalls[i].args[0]).toBe(attendeeArray[i]);
}
}

beforeEach(function() {
collection = Conference.attendeeCollection();
callbackSpy = jasmine.createSpy();
});

it('๋นˆ ์ปฌ๋ ‰์…˜์—์„œ๋Š” ์ฝœ๋ฐฑ์„ ์‹คํ–‰ํ•˜์ง€ ์•Š๋Š”๋‹ค', () => {
collection.iterate(callbackSpy);

expect(callbackSpy).not.toHaveBeenCalled();
});

it('์›์†Œ๊ฐ€ ํ•˜๋‚˜๋ฟ์ธ ์ปฌ๋ ‰์…˜์€ ์ฝœ๋ฐฑ์„ ํ•œ ๋ฒˆ๋งŒ ์‹คํ–‰ํ•œ๋‹ค', () => {
var attendees = [Conference.attendee('์œค์ง€', '๊น€')];

addAttendeesToCollection(attendees);;

collection.iterate(callbackSpy);

verifyCallbackWasExecutedForEachAttendee(attendees);
});

it('์ปฌ๋ ‰์…˜ ์›์†Œ๋งˆ๋‹ค ํ•œ ๋ฒˆ์”ฉ ์ฝœ๋ฐฑ์„ ์‹คํ–‰ํ•œ๋‹ค', () => {
var attendees = [
Conference.attendee('์œค์ง€', '๊น€'),
Conference.attendee('Tom', 'Kazansky'),
Conference.attendee('ํƒœ์˜', '๊น€'),
];

addAttendeesToCollection(attendees);;

collection.iterate(callbackSpy);

verifyCallbackWasExecutedForEachAttendee(attendees);
});
});
});

์ฝœ๋ฐฑ ๊ธฐ๋Šฅ์— ๋‘” ์ด ํ…Œ์ŠคํŠธ๋Š” ๋‹ค์Œ ๋‘ ๊ฐ€์ง€๋ฅผ ํ™•์ธํ•œ๋‹ค.

  • ์ฝœ๋ฐฑ ์‹คํ–‰ ํšŸ์ˆ˜๊ฐ€ ์ •ํ™•ํ•˜๋‹ค.
  • ์ฝœ๋ฐฑ์ด ์‹คํ–‰๋  ๋•Œ๋งˆ๋‹ค ์•Œ๋งž์€ ์ธ์ž๊ฐ€ ์ „๋‹ฌ๋œ๋‹ค.

์ด์ œ attendeeCollection.iterate ํ•จ์ˆ˜๋ฅผ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ๋‹ค.

var Conference = Conference || {};
Conference.attendeeCollection = function() {
var attendees = [];

return {
// ...
getCount: function() {
return attendees.length;
},

iterate: function(callback) {
attendees.forEach(callback);
}
}
}

attendeeCollection ๊ธฐ๋Šฅ ๊ตฌํ˜„์€ ๋งˆ์ณค์ง€๋งŒ, ์ด ์ ˆ ์•ž๋ถ€๋ถ„์—์„œ ์ œ์‹œํ•œ ์š”๊ฑด์ด ์•„์ง ๋‹ค ๋ฐ˜์˜๋œ ๊ฒƒ์€ ์•„๋‹ˆ๋‹ค. ์ฐธ๊ฐ€์ž ์ฒดํฌ์ธ ํ›„ ์™ธ๋ถ€ ์‹œ์Šคํ…œ์— ์ฒดํฌ์ธ์„ ๋“ฑ๋กํ•˜๋Š” ์ฝ”๋“œ๊ฐ€ ์•„์ง ๋‚จ์•˜๋‹ค.

๐ŸŽˆ ์ฝœ๋ฐฑ ํ•จ์ˆ˜์˜ ์ž‘์„ฑ๊ณผ ํ…Œ์ŠคํŒ…โ€‹

attendeeCollection ํ‹€์„ ์žก์•˜์œผ๋‹ˆ ์ถ”๊ฐ€ ๊ธฐ๋Šฅ ๊ตฌํ˜„์€ ๊ฐœ๋ณ„ ์ฐธ๊ฐ€์ž๋ฅผ ์ฒดํฌ์ธํ•˜๋Š” ์ฝœ๋ฐฑ ํ•จ์ˆ˜๋งŒํผ์ด๋‚˜ ๊ฐ„๋‹จํ•˜๋‹ค. ์ฐธ๊ฐ€์ž๋ฅผ ์ฒดํฌ์ธํ•˜๋Š” ์ต๋ช… ํ•จ์ˆ˜๋ฅผ attendeeCollection.iterate ํ•จ์ˆ˜์— ๋ฐ”๋กœ ๋„ฃ๊ธฐ๋งŒ ํ•˜๋ฉด ๋œ๋‹ค.

var attendees = Conference.attendeeCollection();

// UI์—์„œ ์„ ํƒ๋œ ์ฐธ๊ฐ€์ž๋“ค์„ ์ถ”๊ฐ€ํ•œ๋‹ค.
attendees.iterate(function(attendee) {
attendee.checkIn();
// ์™ธ๋ถ€ ์„œ๋น„์Šค์— ์ฒดํฌ์ธ์„ ๋“ฑ๋กํ•œ๋‹ค.
})

ํ•จ์ˆ˜๋ฅผ ์ •์˜ํ•˜๊ธฐ ๋ฌด์„ญ๊ฒŒ ๋‹ค๋ฅธ ํ•จ์ˆ˜์— ์ฝ”๋ฐฑ์œผ๋กœ ๋ฐ”๋กœ ๋„˜๊ธฐ๋Š” ๊ฑด ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ์˜ ๊ฐ•๋ ฅํ•œ ํŠน์„ฑ์ด์ง€๋งŒ, ์ž์นซํ•˜๋ฉด ์ €๋„์—์„œ ๋ฒ—์–ด๋‚  ์šฐ๋ ค๋„ ์žˆ๋‹ค.

์ฒซ์งธ, ์ต๋ช… ์ฝœ๋ฐฑ ํ•จ์ˆ˜๋Š” ์ฝœ๋ฐฑ๋งŒ ๋”ฐ๋กœ ๋–ผ์–ด๋‚ผ ๋ฑ…๋ฒ•์ด ์—†์–ด์„œ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ๊ฐ€ ๋ถˆ๊ฐ€๋Šฅํ•˜๋‹ค. ์ด ์˜ˆ์ œ์—์„œ๋„ ์ฐธ๊ฐ€์ž ์ฒดํฌ์ธ ๊ธฐ๋Šฅ์ด attendeeCollection์— ๋ฌถ์—ฌ ์žˆ์œผ๋ฏ€๋กœ(์ฝœ๋ฐฑ ์‹คํ–‰ ์—ฌ๋ถ€๊ฐ€ ์•„๋‹ˆ๋ผ, ์ฐธ๊ฐ€์ž๋“ค์ด ์ œ๋Œ€๋กœ ์ฒดํฌ์ธํ•ด์„œ ๋“ฑ๋ก ์ฒ˜๋ฆฌ๊ฐ€ ๋๋‚ฌ๋Š”์ง€ ํ…Œ์ŠคํŠธํ•  ์˜๋„๊ฐ€ ์•„๋‹ˆ๋ผ๋ฉด) ์ปฌ๋ ‰์…˜์— ํฌํ•จ๋œ ์ฐธ๊ฐ€์ž๋“ค์˜ ์ฒดํฌ์ธ ์—ฌ๋ถ€๋Š” ์ „์ฒด ์ปฌ๋ ‰์…˜์„ ์ƒ๋Œ€๋กœ ๊ณ„์† ํ…Œ์ŠคํŠธ๋ฅผ ๋ฐ˜๋ณตํ•  ์ˆ˜๋ฐ–์— ์—†๋‹ค.

๋‘˜์งธ, ์ต๋ช… ํ•จ์ˆ˜๋Š” ๋””๋ฒ„๊น…์„ ๋งค์šฐ ์–ด๋ ต๊ฒŒ ๋งŒ๋“ ๋‹ค. ์ต๋ช… ํ•จ์ˆ˜๋Š” ์ •์˜ ์ž์ฒด๊ฐ€ ์ด๋ฆ„ ์—†๋Š” ํ•จ์ˆ˜๋ผ์„œ ๋””๋ฒ„๊ฑฐ๊ฐ€ ํ˜ธ์ถœ ์Šคํƒ์— ์‹๋ณ„์ž๋ฅผ ํ‘œ์‹œํ•  ์ˆ˜ ์—†๋‹ค. ์ฐธ์กฐํ•  ํ•จ์ˆ˜๋ช…์ด ์—†๋Š” ํ•จ์ˆ˜๋Š” ์‹คํ–‰ ์ฝ˜ํ…์ŠคํŠธ๋ฅผ ๋ถ„๊ฐ„ํ•˜๊ธฐ ์–ด๋ ต๊ณ  ๊ฒฐ๊ณผ์ ์œผ๋กœ ๋””๋ฒ„๊น… ์ž์ฒด๊ฐ€ ๋…น๋กํ•˜์ง€ ์•Š๋‹ค.
ํ•˜์ง€๋งŒ ์ฝœ๋ฐฑ ํ•จ์ˆ˜์—๋„ ์ด๋ฆ„์„ ๋ถ™์ผ ์ˆ˜ ์žˆ๋Š”๋ฐ, ๊ทธ๋ ‡๋‹ค๊ณ  ํ•ด์„œ ํ…Œ์ŠคํŠธ์„ฑ์ด ๋” ์ข‹์•„์ง€๋Š” ๊ฑด ์•„๋‹ˆ์ง€๋งŒ, ์ ์–ด๋„ ๋””๋ฒ„๊น… ์ž‘์—…์€ ํ•œ๊ฒฐ ์ˆ˜์›”ํ•ด์ง„๋‹ค.

var attendees = Conference.attendeeCollection();

// UI์—์„œ ์„ ํƒ๋œ ์ฐธ๊ฐ€์ž๋“ค์„ ์ถ”๊ฐ€ํ•œ๋‹ค.
attendees.iterate(function doCheckIn(attendee) {
attendee.checkIn();
// ์™ธ๋ถ€ ์„œ๋น„์Šค๋ฅผ ํ†ตํ•ด ์ฒดํฌ์ธ ๋“ฑ๋กํ•œ๋‹ค.
});

์ด์ œ ํ˜ธ์ถœ ์Šคํƒ ๋ชฉ๋ก์— ํ•จ์ˆ˜๋ช…์ด ํ‘œ์‹œ๋˜์–ด ์ฝ˜ํ…์ŠคํŠธ๋ฅผ ํŒŒ์•…ํ•  ์ˆ˜ ์žˆ๊ณ  ๋””๋ฒ„๊น… ์ž‘์—…์ด ํŽธํ•ด์กŒ๋‹ค.

๋‹ค๋ฅธ ๋ฐฉ๋ฒ•์œผ๋กœ ์ฐธ๊ฐ€์ž ์ฒดํฌ์ธ์€ ์ค‘์š”ํ•œ ๊ธฐ๋Šฅ ์š”๊ฑด์ด๋ฏ€๋กœ ๊ฐ€๋ ฅ checkInService ๊ฐ™์€ ์ž์ฒด ๋ชจ๋“ˆ์— ์บก์Šํ™”ํ•˜๋ฉด ํ…Œ์ŠคํŠธ ๊ฐ€๋Šฅใ…‡ํ•œ ๋‹จ์œ„๋กœ ๋งŒ๋“ค ์ˆ˜ ์žˆ๊ณ  ์ฒดํฌ์ธ ๋กœ์ง์„ attendeeCollection์—์„œ ๋ถ„๋ฆฌํ•˜์—ฌ ์ฝ”๋“œ๋ฅผ ์žฌ์‚ฌ์šฉํ•  ์ˆ˜๋„ ์žˆ๋‹ค.

TDD ๋ฐฉ์‹์œผ๋กœ ๊ฐœ๋ฐœํ•˜๋ฉด ์ฆ‰ํฅ์ ์ด๊ณ  ๋‘์„œ์—†๋Š” ์ฝ”๋“œ๊ฐ€ ๋งŒ๋“ค์–ด์งˆ๊นŒ ๋ด ์—ผ๋ คํ•˜๋Š” ์‚ฌ๋žŒ๋“ค์ด ๋งŽ์ง€๋งŒ, ์˜คํžˆ๋ ค ํ…Œ์ŠคํŠธ ์„ธ๋ถ€๋ฅผ ์ฃผ์˜ ๊นŠ๊ฒŒ ๋ฐ”๋ผ๋ณผ ์ˆ˜ ์žˆ์–ด ํ”„๋กœ๊ทธ๋žจ ๊ตฌ์กฐ๊ฐ€ ๊ฐœ์„ ๋˜๋Š” ํšจ๊ณผ๊ฐ€ ์žˆ๋‹ค.

๋‹ค์Œ ์˜ˆ๋Š” checkInService.checkIn ํ•จ์ˆ˜์˜ ๊ธฐ๋ณธ ๊ธฐ๋Šฅ์„ ์ ์ ํ•˜๋Š” ํ…Œ์ŠคํŠธ ๊พธ๋Ÿฌ๋ฏธ๋‹ค.

describe('Conference.checkInService', () => {
var checkInService, checkInRecorder, attendee;

beforeEach(() => {
checkInRecorder = Conference.checkInRecorder();
spyOn(checkInRecorder, 'recordCheckIn');

// checkInRecorder๋ฅผ ์ฃผ์ž…ํ•˜๋ฉด์„œ
// ์ด ํ•จ์ˆ˜์˜ recordCheckIn ํ•จ์ˆ˜์— ์ŠคํŒŒ์ด๋ฅผ ์‹ฌ๋Š”๋‹ค.
checkInService = Conference.checkInService(checkInRecorder);

attendee = Conference.attendee('ํ˜•์ฒ ', '์„œ');
});

describe('checkInService.checkIn(attendee)', () => {
it('์ฐธ๊ฐ€์ž๋ฅผ ์ฒดํฌ์ธ ์ฒ˜๋ฆฌํ•œ ๊ฒƒ์œผ๋กœ ํ‘œ์‹œํ•œ๋‹ค', () => {
checkInService.checkIn(attendee);
expect(attendee.isCheckedIn()).toBe(true);
});

it('์ฒดํฌ์ธ์„ ๋“ฑ๋กํ•œ๋‹ค', () => {
checkInService.checkIn(attendee);
expect(checkInRecorder.recordCheckIn).toHaveBeenCalledWith(attendee);
});
});
});

checkInService.checkIn ๋‹จ์œ„ ํ…Œ์ŠคํŠธ๊ฐ€ ๋งŒ๋“ค์–ด์กŒ๊ณ , checkInService ๊ตฌํ˜„ ์—ญ์‹œ ๊ฐ„๋‹จํ•˜๋‹ค.

var Conference = Conference || {};

Conference.checkInService = function(checkInRecorder) {
// ์ฃผ์ž…ํ•œ checkInRecorder์˜ ์ฐธ์กฐ๊ฐ’์„ ๋ณด๊ด€ํ•œ๋‹ค.
var recorder = checkInRecorder;

return {
checkIn: function(attendee) {
attendee.checkIn();
recorder.recordCheckIn(attendee);
}
};
};

๋งˆ์ง€๋ง‰์œผ๋กœ ์ฝœ๋ฐฑ ํŒจํ„ด์œผ๋กœ ์ฝ˜ํผ๋Ÿฐ์Šค ์ฐธ๊ฐ€์ž๋ฅผ ์ฒดํฌ์ธํ•˜๋Š” ๊ธฐ๋Šฅ์ด ํ…Œ์ŠคํŠธ๋ฅผ ํ†ต๊ณผํ•œ, ๋…๋ฆฝ์ ์ด๊ณ  ๋ฏฟ์Œ์„ฑ ์žˆ๋Š” 3๊ฐœ์˜ ๋ชจ๋“ˆ๋กœ ์„œ๋กœ ์ž˜ ์กฐํ™”๋˜์–ด ์‹คํ–‰๋˜๋Š” ๋ชจ์Šต์ด๋‹ค.

var checkInService = Conference.checkInService(Conference.checkInRecorder());
var attendees = Conference.attendeeCollection();

// UI์—์„œ ์„ ํƒ๋œ ์ฐธ๊ฐ€์ž๋“ค์„ ์ปฌ๋ ‰์…˜์— ์ถ”๊ฐ€ํ•œ๋‹ค.
attendees.iterate(checkInService.checkIn);

๐Ÿ“š ๋ฌธ์ œ ์˜ˆ๋ฐฉโ€‹

์ต๋ช… ํ•จ์ˆ˜๊ฐ€ ์‹ค์ œ๋กœ ์ฝ”๋“œ์˜ ๊ด€์‹ฌ์‚ฌ๋ฅผ ๋ถ„๋ฆฌํ•˜๊ณ  ํ…Œ์ŠคํŠธํ•˜๊ธฐ ์–ด๋ ต๊ฒŒ ๋งŒ๋“ค์–ด ๊ฒฐ๊ตญ ๋ฏฟ์Œ์„ฑ์ด ๋–จ์–ด์ง„๋‹ค๋Š” ์ด์•ผ๊ธฐ๋„ ํ–ˆ๋‹ค.

๊ทธ๋Ÿฐ๋ฐ ์ฝœ๋ฐฑ ํŒจํ„ด์˜ ๋ฏฟ์Œ์„ฑ์„ ๋Œ์–ด๋‚ด๋ฆฌ๋Š” ์›์ธ์€ ์ต๋ช… ์ฝœ๋ฐฑ ํ•จ์ˆ˜๋ฟ๋งŒ์ด ์•„๋‹ˆ๋‹ค. ์ฝœ๋ฐฑ ํ™”์‚ด์ด๋ผ๋Š” ๊ณจ์นซ๋ฉ์ด์™€ ์ฝœ๋ฐฑ ํ•จ์ˆ˜์—์„œ ์—‰๋šฑ์•ˆ ๊ฐ’์„ ๊ฐ€๋ฆฌํ‚ค๋Š” this, ๋‘ ๊ฐ€์ง€ ๋ฌธ์ œ์ ์„ ์–ด๋–ป๊ฒŒ ์˜ˆ๋ฐฉํ•  ์ˆ˜ ์žˆ๋Š”์ง€ ์•Œ์•„๋ณธ๋‹ค.

๐ŸŽˆ ์ฝœ๋ฐฑ ํ™”์‚ด ๋ˆŒ๋Ÿฌ ํŽด๊ธฐโ€‹

์ฝœ๋ฐฑ ํ™”์‚ด(callback arrow)์€ ์ฝœ๋ฐฑ ํ•จ์ˆ˜๋ฅผ ๋‚จ๋ฐœํ•œ ๊ทน๋‹จ์ ์ธ ์ผ€์ด์Šค๋‹ค. ๋‹ค์Œ ์˜ˆ์ œ๋ฅผ ์‚ดํŽด๋ณด์ž.

CallbackArrow = CallbackArrow || {};

CallbackArrow.rootFunction = function() {
CallbackArrow.firstFunction(function(arg) {
// ์ฒซ ๋ฒˆ์งธ ์ฝœ๋ฐฑ ๋กœ์ง
CallbackArrow.secondFunction(function(arg) {
// ๋‘ ๋ฒˆ์ฉจ ์ฝœ๋ฐฑ ๋กœ์ง
CallbackArrow.thirdFunction(function(arg) {
// ์„ธ ๋ฒˆ์งธ ์ฝœ๋ฐฑ ๋กœ์ง€
CallbackArrow.fourthFunction(function(arg) {
// ๋„ค ๋ฒˆ์งธ ์ฝœ๋ฐฑ ๋กœ์ง
});
});
});
});
};

CallbackArrow.firstFunction = function(callback1) {
callback1(arg);
};
CallbackArrow.secondFunction = function(callback2) {
callback2(arg);
};
CallbackArrow.thirdFunction = function(callback3) {
callback3(arg);
};
CallbackArrow.fourthFunction = function(callback4) {
callback4(arg);
};

์„œ๋กœ ์ค‘์ฒฉ๋œ ์ฝœ๋ฐฑ๋“ค์ด ์ ์  ๊นŠ์€ ๋Šช์— ๋น ์ ธ๋“œ๋Š” ๋ชจ์Šต์ด ์™ผ์ชฝ์—์„œ ์˜ค๋ฅธ์ชฝ์œผ๋กœ ํ–ฅํ•˜๋Š” ๊ณต๋ฐฑ ํ™”์‚ดํ‘œ ํ˜•์ƒ์„ ๋ค๋‹ค. ์ด ์ฝ”๋“œ๋Š” ์ฝ๊ธฐ๋Š” ๋ฌผ๋ก  ๊ณ ์น˜๊ธฐ๋„ ์–ด๋ ต๊ณ  ๋‹จ์œ„ ํ…Œ์ŠคํŠธ๋Š” ์‚ฌ์‹ค ๋ถˆ๊ฐ€๋Šฅํ•˜๋‹ค.

์ด๋Ÿด ๊ฒฝ์šฐ, ์ต๋ช… ํ•จ์ˆ˜์— ์ด๋ฆ„์„ ๋ถ™์—ฌ ๋–ผ์–ด ๋†“๊ธฐ๋งŒ ํ•ด๋„ ์ƒํ™ฉ์€ ํ›จ์”ฌ ํ˜ธ์ „๋œ๋‹ค. ๋‹ค์Œ์ฒ˜๋Ÿผ ๋ฆฌํŒฉํ† ๋งํ•˜์ž.

CallbackArrow = CallbackArrow || {};

CallbackArrow.rootFunction = function() {
CallbackArrow.firstFunction(CallbackArrow.firstCallback);
};
CallbackArrow.firstFunction = function(callback1) {
callback1(arg);
};
CallbackArrow.secondFunction = function(callback2) {
callback2(arg);
};
CallbackArrow.thirdFunction = function(callback3) {
callback3(arg);
};
CallbackArrow.fourthFunction = function(callback4) {
callback4(arg);
};

CallbackArrow.firstCallback = function() {
// ์ฒซ ๋ฒˆ์งธ ์ฝœ๋ฐฑ ๋กœ์ง
CallbackArrow.secondFunction(CallbackArrow.secondCallback);
};
CallbackArrow.secondCallback = function() {
// ๋‘ ๋ฒˆ์งธ ์ฝœ๋ฐฑ ๋กœ์ง
CallbackArrow.thirdFunction(CallbackArrow.thirdCallback);
};
CallbackArrow.thirdCallback = function() {
// ์„ธ ๋ฒˆ์งธ ์ฝœ๋ฐฑ ๋กœ์ง
CallbackArrow.fourthFunction(CallbackArrow.fourthCallback);
};
CallbackArrow.fourthFunction = function() {
// ๋„ค ๋ฒˆ์งธ ์ฝœ๋ฐฑ ๋กœ์ง
};

์ด์ฒ˜๋Ÿผ ์ค‘์ฒฉ ์ฝœ๋ฐฑ์„ ๋ˆŒ๋Ÿฌ ํŽธ ์ฝ”๋“œ๊ฐ€ ๋” ๋‚ซ๋‹ค. ๋ฌด์—‡๋ณด๋‹ค ๋‹จ์œ„ ํ…Œ์ŠคํŠธ๋ฅผ ์ „์ ์œผ๋กœ ํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ์ ์ด ์ž๋ž‘์ด๋‹ค. ์ค‘์ฒฉ ์ต๋ช… ์ฝœ๋ฐฑ ํ•จ์ˆ˜์˜ ๋ชจ๋“  ๊ธฐ๋Šฅ์„ ๊ทธ ์ž์ฒด๋งŒ์œผ๋กœ ๋‹จ์œ„ ํ…Œ์ŠคํŠธํ•  ์ˆ˜ ์žˆ๋Š” CallbackArrow์˜ ํ•จ์ˆ˜ ํ”„๋กœํผํ‹ฐ๋กœ ๋นผ๋‚ธ ๋•๋ถ„์ด๋‹ค. ๋”๊ตฌ๋‚˜ ์ฝœ๋ฐฑ ํ•จ์ˆ˜๋งˆ๋‹ค ๋ช…์ฐฐ์ด ๋‹ฌ๋ ค ์žˆ์œผ๋‹ˆ ๋””๋ฒ„๊น… ๋„๊ตฌ์—์„œ ์Šคํƒ ์ถ”์  ์‹œ ์“ด์›ƒ์Œ์„ ์ง€์„ ์ผ๋„ ์—†์„ ๊ฒƒ์ด๋‹ค.

๐ŸŽˆ this๋ฅผ ์กฐ์‹ฌํ•˜๋ผโ€‹

์ „ํ˜€ ์—‰๋šฑํ•œ ๊ฐ’์ด ์ฐธ์กฐํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ์ฝœ๋ฐฑ ํ•จ์ˆ˜์—์„œ this ๋ณ€์ˆ˜๋ฅผ ์ฐธ์กฐํ•  ๋–„๋Š” ์กฐ์‹ฌํ•ด์•ผ ํ•œ๋‹ค.

์Šนํ˜„์€ ์ฒดํฌ์ธ์„ ๋งˆ์นœ attendeeCollection์˜ attendee ๊ฐ์ฒด ์ˆ˜๋ฅผ ์„ธ๋Š” checkedInAttendeeCounter ๋ชจ๋“ˆ์„ ๊ตฌํ˜„ํ•˜๋ ค๊ณ  ํ•œ๋‹ค. checkInService์™€ ํฌ๊ฒŒ ๋‹ค๋ฅผ ๋ฐ” ์—†์ด attendeeCollection.iterate์— ํ‘œ์ถœํ•  ํ•จ์ˆ˜๋ฅผ ๋„˜๊ธฐ๋Š” ์‹์œผ๋กœ ์ž‘์„ฑํ•˜๋ฉด ๋œ๋‹ค. checkedInAttendeeCounter์˜ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ๋จผ์ € ์ž‘์„ฑํ•œ๋‹ค.

describe('Conference.checkedInAttendeeCounter', () => {
var counter;

beforeEach(() => {
counter = Conference.checkedInAttendeeCounter();
});

describe('increment()', () => {
// increment ํ…Œ์ŠคํŠธ
});

describe('getCount()', () => {
// getCount ํ…Œ์ŠคํŠธ
});

describe('countIfCheckedIn(attendee)', () => {
var attendee;

beforeEach(() => {
attendee = Conference.attendee('ํƒœ์˜', '๊น€');
});

it('์ฐธ๊ฐ€์ž๊ฐ€ ์ฒดํฌ์ธํ•˜์ง€ ์•Š์•˜์œผ๋ฉด ์ธ์›์ˆ˜๋ฅผ ์„ธ์ง€ ์•Š๋Š”๋‹ค.', () => {
counter.countIfCheckedIn(attendee);
expect(counter.getCount()).toBe(0);
});

it('์ฐธ๊ฐ€์ž๊ฐ€ ์ฒดํฌ์ธํ•˜๋ฉด ์ธ์›์ˆ˜๋ฅผ ์„ผ๋‹ค', () => {
attendee.checkIn();
counter.countIfCheckedIn(attendee);
expect(counter.getCount()).toBe(1);
});
});
});

์ด์–ด์„œ Conference.checkedInAttendeeCounter ๊ตฌํ˜„๋ถ€์ด๋‹ค.

var Conference = Conference || {};

Conference.checkedInAttendeeCounter = function() {
var checkedInAttendees = 0;

return {
increment: function() {
checkedInAttendees++;
},
getCount: function() {
return checkedInAttendees;
},
countIfCheckedIn: function(attendee) {
if (attendee.isCheckedIn()) {
this.increment();
}
}
}
}

์—ฌ๊ธฐ์„œ this.increment๋ฅผ ๋ณด๋ฉด ๋‹จ์œ„ ํ…Œ์ŠคํŠธ๋Š” ํ†ต๊ณผํ•˜์ง€๋งŒ checkedInAttendeeCounter๋ฅผ ์ •๋ง attendeeCollection ์ธ์Šคํ„ด์Šค์™€ ํ•จ๊ป˜ ์“ธ ์ˆ˜ ์žˆ์„๊นŒ?

var checkInService = Conference.checkInService(Conference.checkInRecorder());
var attendees = Conference.attendeeCollection();
var counter = Conference.checkedInAttendeeCounter();

// UI์—์„œ ์„ ํƒํ•œ ์ฐธ๊ฐ€์ž๋“ค์„ ์ฐธ๊ฐ€์ž ์ปฌ๋ ‰์…˜์— ์ถ”๊ฐ€ํ•œ๋‹ค.
attendees.add(Conference.attendee('์œค์ง€', '๊น€'));
attendees.add(Conference.attendee('์Šน๋ฏผ', '์‚ฌ'));

// ์ฐธ์„์ž๋“ค์„ ์ฒดํฌ์ธํ•œ๋‹ค.
attendees.iterate(checkInService.checkIn);

// ์ฒดํฌ์ธ์„ ๋งˆ์นœ ์ฐธ๊ฐ€์ž ์ธ์›์ˆ˜๋ฅผ ์„ธ์–ด๋ณธ๋‹ค.
attendees.iterate(counter.countIfCheckedIn);

console.log(counter.getCount()); // 0 ์–ด๋ž??ใ„ด

์œ„ ์˜ˆ์ œ์˜ ๊ฒฐ๊ณผ๋Š” 2๊ฐ€ ๋‚˜์™€์•ผ ํ•œ๋‹ค. attendeeCollection.iterator ์‹คํ–‰ ์‹œ checkedInAttendeeCounter์—์„œ this๊ฐ€ ์‹ค์ œ๋กœ ๊ฐ€๋ฆฌํ‚จ ๊ฐ’์€ checkedInAttendeeCounter๊ฐ€ ์•„๋‹ˆ๋ผ ์ „์—ญ window ๊ฐ์ฒด์ž„์„ ์•Œ ์ˆ˜ ์žˆ๋‹ค.

์ผ๋ฐ˜์ ์œผ๋กœ this ๊ฐ’์€ ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•œ (๋Œ€๊ฐœ ํ•จ์ˆ˜ ์•ž์— ์ ์œผ๋กœ ์—ฐ๊ฒฐํ•œ) ๊ฐ์ฒด๋ฅผ ๊ฐ€๋ฆฌํ‚ค์ง€๋งŒ, ์ฝœ๋ฐฑ ํ•จ์ˆ˜๋ฅผ ๋งŒ๋“ค์–ด ๋„ฃ์„ ๋•Œ ์–ด๋–ค ๊ฐ์ฒด๋ฅผ ์ฐธ์กฐํ•˜๋ผ๊ณ  ์ง์ ‘ ์ง€์ •ํ•  ์ˆ˜๋Š” ์—†๋‹ค. ์ด๋Ÿฐ ์ด์œ ๋กœ ์ฝœ๋ฐฑ ํ•จ์ˆ˜๋Š” ๋Œ€๋ถ€๋ถ„ this๋ฅผ ๋ช…์‹œ์ ์œผ๋กœ ๊ฐ€๋ฆฌํ‚จ๋‹ค.

attendeeCollection.iterate์—์„œ forEach๋Š” ์ฝœ๋ฐฑ ๋‚ด๋ถ€์—์„œ ์ฐธ์กฐํ•  ๊ฐ์ฒด๋ฅผ ๋‘ ๋ฒˆ์งธ ์ธ์ž๋กœ ์ „๋‹ฌํ•จ์œผ๋กœ์จ this๊ฐ€ ๋ฌด์—‡์„ ์ฐธ์กฐํ•ด์•ผ ํ•˜๋Š”์ง€ ๋ฐํž ์ˆ˜ ์žˆ๋‹ค. attendeeCollection.iterate ๊ฐœ๋ฐœ ๋‹ด๋‹น์ž ์Šนํ˜„์€ attendeeCollection.iterate๋กœ ํ•˜์—ฌ๊ธˆ this๊ฐ€ ์ฐธ์กฐํ•  ๊ฐ์ฒด๋ฅผ ๋‘ ๋ฒˆ์งธ ์ธ์ž๋กœ ๋ฐ›์•„ forEach์— ๊ทธ๋Œ€๋กœ ๋„˜๊ฒจ์ฃผ๊ฒŒ๋” ๊ณ ์นœ๋‹ค. ์ด๋ ‡๊ฒŒ ํ•ด์„œ countIfCheckedIn ํ•จ์ˆ˜์— ๊ฑธ๋งž์€ this๋ฅผ checkedInAttendeeCounter ์ธ์Šคํ„ด์Šค์— ๋ฌถ์–ด๋‘˜ ์ˆ˜ ์žˆ๋‹ค.

๊ทธ๋Ÿฌ๋‚˜ ๋งŒ์ผ attendeeCollection์ด ์š‰ ์—…์ฒด๊ฐ€ ๋‚ฉํ’ˆํ•œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์ฝ”๋“œ๋ผ ์Šนํ˜„์ด ์ˆ˜์ •ํ•  ์ˆ˜ ์—†๋Š” ๊ฒฝ์šฐ๋ผ๋ฉด ์ž‘์„ฑํ•œ ์ฝœ๋ฐฑ์—์„œ ์•ˆ์ •์ ์œผ๋กœ ํ˜„์žฌ ๊ฐ์ฒด ์ธ์Šคํ„ด์Šค๋ฅผ ๊ฐ€๋ฆฌํ‚ค๋„๋ก ํ•  ์ˆ˜ ์žˆ๋‹ค.

์šฐ์„ , checkedInAttendeeCounter.countIfCheckedIn ์‹คํ–‰ ์‹œ this๊ฐ€ checkedInAttendeeCounter ์ธ์Šคํ„ด์Šค ์ด์™ธ์˜ ๊ฐ์ฒด๋ฅผ ๊ฐ€๋ฆฌํ‚ค๋Š” ์ƒํ™ฉ์„ ๊ฐ€์ •ํ•œ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ๋ฅผ ์งœ๋ณด์ž.

๋ฒ„๊ทธ๊ฐ€ ์žˆ๋‹ค๋Š” ๊ฑด ํ…Œ์ŠคํŠธ ๊พธ๋Ÿฌ๋ฏธ๊ฐ€ ์•„์ง ๋œ ๋๋‹ค๋Š” ๋ฐ˜์ฆ์ด๋‹ค. ํ•ญ์ƒ ๋ฒ„๊ทธ๋ฅผ ๊ณ ์น˜๊ธฐ ์ „์— ์‹คํŒจํ•  ํ…Œ์ŠคํŠธ๋ฅผ ๋จผ์ € ์ž‘์„ฑํ•˜๋ผ.

describe('Conference.checkedInAttendeeCounter', () => {
var counter;

// ์ด์ „ ํ…Œ์ŠคํŠธ ์ค„์ž„

describe('countIfCheckedIn(attendee)', () => {
var attendee;

beforeEach(() => {
attendee = Conference.attendee('ํƒœ์˜', '๊น€');
});

//์ด์ „ ํ…Œ์ŠคํŠธ ์ค„์ž„

it('this๊ฐ€ ๊ผญ checkedInAttendeeCounter ์ธ์Šคํ„ด์Šค๋ฅผ ๊ฐ€๋ฆฌํ‚ค๋Š” ๊ฒƒ์€ ์•„๋‹ˆ๋‹ค', () => {
attendee.checkIn();
// this์— ๋นˆ ๊ฐ์ฒด๋ฅผ ๋„ฃ๊ณ 
// counter.countIfCheckedIn์„ ์‹คํ–‰ํ•œ๋‹ค.
counter.countIfCheckIn.call({}, attendee);
expect(counter.getCount()).toBe(1);
});
});
});

Conference.checkedInAttendeeCounter ์ž์‹ ์˜ ์ฐธ์กฐ๊ฐ’์„ self๋ผ๋Š” ๋ณ€์ˆ˜์— ๋‹ด๊ณ  countIfCheckedIn์—์„œ this ๋Œ€์‹  self๋กœ ์ฐธ์กฐํ•˜๋ฉด getCount ํ•จ์ˆ˜๋ฅผ ํ™•์‹คํžˆ ๋ฐ”๋ผ๋ณผ ์ˆ˜ ์žˆ์„ ๊ฒƒ์ด๋‹ค.

var Conference = Conference || {};

Conference.checkedInAttendeeCounter = function() {
var checkedInAttendees = 0;
var self = {
increment: function() {
checkedInAttendees += 1;
},
getCount: function() {
return checkedInAttendees;
},
countIfCheckedIn: function(attendee) {
if (attendee.isCheckedIn()) {
self.increment();
}
}
};

return self;
};

๐Ÿ“š ์ •๋ฆฌํ•˜๊ธฐโ€‹

ํ…Œ์ŠคํŠธ๋ฅผ ๋จผ์ € ์ž‘์„ฑํ•˜๊ณ  ์ฝœ๋ฐฑ ํ•จ์ˆ˜๋ฅผ ์—ฌ๋Ÿฟ ํฌํ•จํ•œ ์›์ฝ”๋“œ๋ฅผ ๊ตฌํ˜„ํ•˜๋ฉด์„œ this๊ฐ€ ๋œป๋ฐ–์— ์—‰๋šฑํ•œ ์ฐธ์กฐ๋ฅผ ํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ์‚ฌ์‹ค์„ ์•Œ์•˜๋‹ค. ๋˜ํ•œ, ์ต๋ช… ์ฝœ๋ฐฑ ํ•จ์ˆ˜๊ฐ€ ์–ผ๋งˆ๋‚˜ ํ…Œ์ŠคํŠธํ•˜๊ธฐ ์–ด๋ ค์šด์ง€, ์—ฌ๊ธฐ์— ์ค‘์ฒฉ๊นŒ์ง€ ๋”ํ•˜๋ฉด ๊ณจ์นซ๋ฉ์ด ์ฝœ๋ฐฑ ํ™”์‚ด์ด ๋˜๊ณ  ๋งŒ๋‹ค๋Š” ์‚ฌ์‹ค์„ ๋ฐฐ์› ๋‹ค.