본문으둜 κ±΄λ„ˆλ›°κΈ°

🌈 Chapter 10: νŒ©ν† λ¦¬ νŒ¨ν„΄

πŸ“š λ‹¨μœ„ ν…ŒμŠ€νŠΈβ€‹

μš”κ΅¬μ‚¬ν•­: μžλ°”μŠ€ν¬λ¦½νŠΈ μ½˜νΌλŸ°μŠ€λŠ” λ§Žμ€ ν”„λ ˆμ  ν…Œμ΄μ…˜μœΌλ‘œ 풍성할 것이닀. μŠΉν˜„μ€ ν”„λ ˆμ  ν…Œμ΄μ…˜ λͺ¨λΈλ§μ„ κ³ λ―Ό 쀑이닀.
ν”„λ ˆμ  ν…Œμ΄μ…˜ μœ ν˜•μ€ 두 가지, 즉 일반(regular) ν”„λ ˆμ  ν…Œμ΄μ…˜κ³Ό 벀더(vendor) ν”„λ ˆμ  ν…Œμ΄μ…˜μœΌλ‘œ λ‚˜λ‰œλ‹€. 일반 ν”„λ ˆμ  ν…Œμ΄μ…˜μ€ 제λͺ©κ³Ό λ°œν‘œμž(선택) 정보가 있고, 벀더 ν”„λ ˆμ  ν…Œμ΄μ…˜μ€ 여기에 벀더λͺ…, μ œν’ˆ(선택) 정보가 μΆ”κ°€λœλ‹€.

일벀 ν”„λ ˆμ  ν…Œμ΄μ…˜λ²€λ” ν”„λ ˆμ  ν…Œμ΄μ…˜
title(제λͺ©)ν•„μˆ˜ν•„μˆ˜
presenter(λ°œν‘œμž)선택선택
vendor(벀더)-ν•„μˆ˜
product(μ œν’ˆ)-선택

μŠΉν˜„μ€ Object.create λ©”μ„œλ“œλ‘œ ν”„λ‘œν† νƒ€μž… 상속을 해보렀고 λ‹€μŒμ²˜λŸΌ 일반, 벀더 ν”„λ ˆμ  ν…Œμ΄μ…˜ 클래슀λ₯΄ μ½”λ”©ν•œλ‹€.

일반 ν”„λ ˆμ  ν…Œμ΄μ…˜
// 일반 ν”„λ ˆμ  ν…Œμ΄μ…˜
var Conference = Conference || {};
Conference.Presentation = function(title, presenter) {
'use strict';

if (!(this instanceof Conference.Presentation)) {
throw new Error(Conference.Presentation.messages.mustUseNew);
}

if (!title) {
throw new Error(Conference.Presentation.messages.titleRequired);
}

this.title = title;
this.presenter = presenter;
};

Conference.Presentation.messages = {
mustUseNew: 'Presentation은 λ°˜λ“œμ‹œ "new"둜 생성해야 ν•©λ‹ˆλ‹€.',
titleRequired: 'title은 ν•„μˆ˜ μž…λ ₯ ν•­λͺ©μž…λ‹ˆλ‹€.',
};
  • 벀더 ν”„λ ˆμ  ν…Œμ΄μ…˜
벀더 ν”„λ ˆμ  ν…Œμ΄μ…˜
// 벀더 ν”„λ ˆμ  ν…Œμ΄μ…˜
var Conference = Conference || {};
Conference.VendorPresentation = function(title, presenter, vendor, product) {
'use strict';

if (!(this instanceof Conference.VendorPresentation)) {
throw new Error(Conference.VendorPresentation.messages.mustUseNew);
}

if (!vendor) {
throw new Error(Conference.VendorPresentation.messages.vendorRequired);
}

// 싀직적인 상속은 μƒμ„±μžμ— μžˆλŠ” λ‹€μŒ μ½”λ“œμ—μ„œ μΌμ–΄λ‚œλ‹€.
Conference.VendorPresentation.call(this, title, presenter);
this.vendor = vendor;
this.presenter = presenter;
};

// ν”„λ‘œν† νƒ€μž… 상속이 이루어진닀.
Conference.VendorPresentation.prototype
= Object.create(Conference.Presentation.prototype);

Conference.VendorPresentation.messages = {
mustUseNew: 'VendorPresentation은 λ°˜λ“œμ‹œ "new"둜 생성해야 ν•©λ‹ˆλ‹€.',
vendorRequired: 'vendor은 ν•„μˆ˜ μž…λ ₯ ν•­λͺ©μž…λ‹ˆλ‹€.',
};

μ½”λ“œλŠ” λ¬Έμ œμ—†μ΄ λŒμ•„κ°€μ§€λ§Œ, λ°œν‘œμž 없이 VendorPresentation을 μƒμ„±ν•˜λŠ” 광경이 쑰금 λΆ€μžμ—°μŠ€λŸ½λ‹€.

new VendorPresentation('The title', undefined, 'The Vendor', 'The Product');

λΉ„λ””μ˜€, μ„Έλ―Έλ‚˜ λ“± μœ ν˜•μ΄ μ „ν˜€ λ‹€λ₯Έ ν”„λ ˆμ  ν…Œμ΄μ…˜λ„ μžˆλ‹€. μ΄λ“€κΉŒμ§€ Presentation 객체λ₯Ό μƒμ†ν•˜λ©΄ μ΄μƒν•œ μƒμ„±μžλ₯Ό κ°–κ²Œ λ˜μ§€ μ•Šμ„κΉŒ? λ˜ν•œ, ν”„λ ˆμ  ν…Œμ΄μ…˜ μœ ν˜•μ€ λ„€ μ½”λ“œμ—μ„œ μ•Œμ•„μ„œ νŒŒμ•…ν•΄μ•Ό ν•΄μ•Όλ˜μ§€ μ•Šμ„κΉŒ?

presentationFactory 객체에 create λ©”μ„œλ“œλ₯Ό λ§Œλ“€κ³  ν”„λ‘œνΌν‹° λ­‰μΉ˜λ‘œ 이루어진 νŒŒλΌλ―Έν„°λ₯Ό ν•˜λ‚˜ λ°›μ•„ μ•Œμ•„μ„œ μ²˜λ¦¬μ‹œν‚€λ©΄ 될 κ±° κ°™λ‹€.

describe('presentationFactory', () => {
var factory = Conference.presentationFactory();

describe('create(objectLiteral)', () => {
it('νŒŒλΌλ―Έν„°μ— μ΄μƒν•œ ν”„λ‘œνΌν‹°κ°€ 있으면 μ˜ˆμ™Έλ₯Ό λ˜μ§„λ‹€', () => {
var badProp = 'badProperty';

function createWithUnexpectedProperties() {
var badParam = {};
badParam[badProp] = 'unexpected!';
factory.create(badParam);
}

expect(createWithUnexpectedProperties).toThrowError(
Conference.presentationFactory.messages.unexpectedProperty + badProp
);
});
});
});

presentationFactory의 첫 번째 μž„λ¬΄λŠ” νŒŒλΌλ―Έν„°μ— μ΄μƒν•œ ν”„λ‘œνΌν‹°λŠ” μ—†λŠ”μ§€ μ‚΄ν”ΌλŠ” 일이닀.

var Conference = Conference || {};

Conference.presentationFactory = function presentationFactory() {
'use strict';

return {
// obj 인자의 ν”„λ‘œνΌν‹°μ— 따라
// ν•˜λ‚˜μ˜ Presentation λ˜λŠ” κ·Έ ν•˜μœ„ Presentation 쀑 ν•˜λ‚˜λ₯Ό μƒμ„±ν•œλ‹€.
create: function(obj) {
var baseProperties = ['title', 'presenter'];
var vendorProperties = ['vendor', 'product'];
var allProperties = baseProperties.concat(vendorProperties);
var p;
var ix;

for (p in obj) {
if (allProperties.indexOf(p) < 0) {
throw new Error(
Conference.presentationFactory.messages.unexpectedProperty + p,
);
}
}

// λ‚˜μ€‘μ— Presentationμ—μ„œ μœ λž˜ν•œ 객체λ₯Ό λ°˜ν™˜ν•  μ˜ˆμ •
}
};
};

Conference.presentationFactory.messages = {
unexpectedProperty: 'μ΄μƒν•œ ν”„λ‘œνΌν‹°λ₯Ό μ§€λ‹Œ 생성 νŒŒλΌλ―Έν„°κ°€ μžˆμŠ΅λ‹ˆλ‹€.',
};

λ„€κ±°ν‹°λΈŒ ν…ŒμŠ€νŠΈλ₯Ό ν†΅κ³Όν•˜κ³  κ΄€λ ¨ 코딩이 끝났닀면 λ‹€μŒμ€ νŒ©ν† λ¦¬ λ³Έμ—°μ˜ λ‘œμ§μ„ κ΅¬ν˜„ν•  μ°¨λ‘€λ‹€.

첫째, νŒŒλΌλ―Έν„°μ— κΈ°λ³Έ Presentation 객체의 ν”„λ‘œνΌν‹°λ§Œ 있으면 create λ©”μ„œλ“œλŠ” κ·Έλƒ₯ 이 객체λ₯Ό λ°˜ν™˜ν•œλ‹€. 이런 νŒŒλΌλ―Έν„°κ°€ λ„˜μ–΄μ˜€λ©΄ 정말 Presentation이 λ°˜ν™˜λ˜λŠ”μ§€ ν™•μΈν•˜λŠ” λ‹¨μœ„ ν…ŒμŠ€νŠΈλ₯Ό μž‘μ„±ν•˜κΈ°λŠ” μ‰¬μ›Œλ„ 두 가지 μ€‘μš”ν•œ 고민거리λ₯Ό 남긴닀.

  • Presentation μƒμ„±μžμ— μ˜¬λ°”λ₯Έ νŒŒλΌλ―Έν„°κ°€ μ „λ‹¬λλŠ”μ§€ μ–΄λ–»κ²Œ ν™•μ‹ ν• κΉŒ?
  • κ·Έλ ‡λ‹€κ³  ν•˜λ”λΌλ„ μƒμ„±λœ 객체가 잘 λ°˜ν™˜λ˜μ—ˆλŠ”μ§€ 무슨 수둜 보μž₯ν• κΉŒ?
describe('presentationFactory', () => {
'use strict';
var factory = Conference.presentationFactory();
var baseParameter = {
title: 'μžλ°”μŠ€ν¬λ¦½νŠΈλ₯Ό λ©‹μ§€κ²Œ μ‚¬μš©ν•΄λ³΄μ„Έμš”',
presenter: 'λ°•κΈΈλ²—',
};

describe('create(objectLiteral)', () => {
/** 이전 ν…ŒμŠ€νŠΈ μ€„μž„ **/

describe('κΈ°λ³Έ ν”„λ‘œνΌν‹°λ§Œ μžˆμ„ 경우', () => {
var fakePresentation = {
title: 'ν”„λ ˆμ  ν…Œμ΄μ…˜μ„ λ² λΌλŠ” 방법',
};
var spyOnConstructor;
var returnedPresentation;

beforeEach(() => {
spyOnConstructor = spyOn(Conference, 'Presentation')
.and.returnValue(fakePresentation);
returnedPresentation = factory.create(baseParameter);
});

it('λͺ¨λ“  값을 Presentation μƒμ„±μžμ— λ„˜κΈ΄λ‹€', () => {
expect(spyOnConstructor).toHaveBeenCalledWith(
baseParameter.title, baseParameter.presenter,
);
});

it('Presentation μƒμ„±μžλ₯Ό λ”± ν•œ 번만 ν˜ΈμΆœν•œλ‹€', () => {
expect(spyOnConstructor.calls.count()).toBe(1);
});

it('μƒμ„±μž Presentation을 λ°˜ν™˜ν•œλ‹€', () => {
expect(factory.create(baseParameter)).toBe(fakePresentation);
});
});
});
});

μœ„ 예제의 κΈ°λ³Έ ν”„λ ˆμ  ν…Œμ΄μ…˜μ„ νŒ©ν† λ¦¬κ°€ 잘 μƒμ„±ν•˜λŠ”μ§€ ν™•μΈν•˜λŠ” 일이 거의 λ‹€κ³ , λ‚˜λ¨Έμ§€λŠ” 벀더 ν”„λ ˆμ  ν…Œμ΄μ…˜ κ΄€λ ¨ ν…ŒμŠ€νŠΈλ“€μ΄λ‹€.

describe('presentationFactory', () => {
'use strict';
var factory = Conference.presentationFactory();
var baseParameter = {
title: 'μžλ°”μŠ€ν¬λ¦½νŠΈλ₯Ό λ©‹μ§€κ²Œ μ‚¬μš©ν•΄λ³΄μ„Έμš”',
presenter: 'λ°•κΈΈλ²—',
};

describe('create(objectLiteral)', () => {
/** 이전 ν…ŒμŠ€νŠΈ μ€„μž„ **/

describe('κΈ°λ³Έ ν”„λ‘œνΌν‹°λ§Œ μžˆμ„ 경우', () => {
// ν…ŒμŠ€νŠΈλ₯Ό μ€„μž„
});

describe('VendorPresentation ν”„λ‘œνΌν‹°κ°€ 적어도 ν•˜λ‚˜ 이상 μžˆμ„ 경우', () => {
var vendorParameter = {
title: 'μžλ°”μŠ€ν¬λ¦½νŠΈλ₯Ό λ©‹μ§€κ²Œ μ‚¬μš©ν•΄λ³΄μ„Έμš”',
presenter: 'λ°•κΈΈλ²—',
vendor: 'κΈΈλ²—μΆœνŒμ‚¬',
product: 'μžλ°”μŠ€ν¬λ¦½νŠΈ νŒ¨ν„΄κ³Ό ν…ŒμŠ€νŠΈ',
};
var fakeVendorPresentation = {
title: vendorParameter.title,
};
var spyOnConstructor;

beforeEach(() => {
spyOnConstructor = spyOn(Conference, 'VendorPresentation')
.and.returnValue(fakeVendorPresentation);
});


it('VendorPresentation을 생성해본닀', () => {
var expectedCallCount = 0;

function createParam(propName) {
var param = {};
var p;

for (p in baseParameter) {
param[p] = baseParameter[p];
}

param[propName] = vendorParameter[propName];
return param;
}

// 각 vendor ν”„λ‘œνΌν‹°λ₯Ό μ°¨λ‘€λ‘œ μ§€λ‹Œ νŒŒλΌλ―Έν„°λ₯Ό μƒμ„±ν•œλ‹€.
['vendor', 'product'].forEach(function(propName) {
var param = createParam(propName);
var presentation = factory.create(param);

expect(spyOnConstructor.calls.count()).toBe(++expectedCallCount);
});
});

it('λͺ¨λ“  값을 VendorPresentation μƒμ„±μžμ— λ„˜κΈ΄λ‹€', () => {
factory.create(vendorParameter);

expect(spyOnConstructor).toHaveBeenCalledWith(
vendorParameter.title, vendorParameter.presenter,
vendorParameter.vendor, vendorParameter.product,
);
});

it('VendorPresentation μƒμ„±μžλ₯Ό λ”± ν•œ 번만 ν˜ΈμΆœν•œλ‹€', () => {
factory.create(vendorParameter);

expect(spyOnConstructor.calls.count()).toBe(1);
});

it('μƒμ„±ν•œ VendorPresentation을 λ°˜ν™˜ν•œλ‹€', () => {
expect(factory.create(vendorParameter)).toBe(fakeVendorPresentation);
});
});
});
});

πŸ“š νŒ©ν† λ¦¬ νŒ¨ν„΄ κ΅¬ν˜„β€‹

ν…ŒμŠ€νŠΈλ‘œ κΈ°λŠ₯을 λ‹€ μ κ±΄ν–ˆμœΌλ‹ˆ 이제 νŒ©ν† λ¦¬λ₯Ό κ΅¬ν˜„ν•˜μž.

var Conference = Conference || {};

Conference.presentationFactory = function presentationFactory() {
'use strict';

return {
// obj 인자의 ν”„λ‘œνΌν‹°μ— 따라
// ν•˜λ‚˜μ˜ Presentation λ˜λŠ” κ·Έ ν•˜μœ„ Presentation 쀑 ν•˜λ‚˜λ₯Ό μƒμ„±ν•œλ‹€.
create: function(obj) {
var baseProperties = ['title', 'presenter'];
var vendorProperties = ['vendor', 'product'];
var allProperties = baseProperties.concat(vendorProperties);
var p;
var ix;

for (p in obj) {
if (allProperties.indexOf(p) < 0) {
throw new Error(
Conference.presentationFactory.messages.unexpectedProperty + p,
);
}
}

// μΆ”κ°€
for (ix = 0; ix < vendorProperties.length; ++ix) {
if (obj.hasOwnProperty(vendorProperties[ix])) {
return new Conference.VendorPresentation(
obj.title, obj.presenter, obj.vendor, obj.product,
);
}
}

return new Conference.Presentation(obj.title, obj.presenter);
}
};
};

Conference.presentationFactory.messages = {
unexpectedProperty: 'μ΄μƒν•œ ν”„λ‘œνΌν‹°λ₯Ό μ§€λ‹Œ 생성 νŒŒλΌλ―Έν„°κ°€ μžˆμŠ΅λ‹ˆλ‹€.',
};

νŒ©ν† λ¦¬κ°€ ν•˜λŠ” 일을 μ •λ¦¬ν•΄λ³΄μž.

  • create의 νŒŒλΌλ―Έν„°λŠ”, 이전에 객체 λ¦¬ν„°λŸ΄λ‘œ λ„˜κ²Όμ„ λ•Œ undefined둜 자리 λΌμ›€ν–ˆλ˜ 보기 ν‰ν•œ ν˜•νƒœμ—μ„œ μ™„μ „νžˆ 벗어났닀.
  • νŒŒλΌλ―Έν„°μ— 무엇이든 잘 κ±΄λ„€μ£ΌκΈ°λ§Œ ν•˜λ©΄ 뒷일은 νŒ©ν† λ¦¬κ°€ μ•Œμ•„μ„œ μ²˜λ¦¬ν•œλ‹€.
  • λ‚˜μ€‘μ— μœ ν˜•μ΄ λ‹€λ₯Έ ν”„λ ˆμ  ν…Œμ΄μ…˜λ„ μ–Όλ§ˆλ“ μ§€ μΆ”κ°€ν•  수 μžˆλ‹€.
  • new ν‚€μ›Œλ“œλ‘œ 객체λ₯Ό 생성해야 ν•œλ‹€λŠ” 사싀을 νŒ©ν† λ¦¬κ°€ λŒ€μ‹  κΈ°μ–΅ν•΄μ€€λ‹€.

πŸ“š λ‹€λ₯Έ νŒ©ν† λ¦¬ μœ ν˜•β€‹

였직 ν•œ 가지 μœ ν˜•μ˜ 객체만 μƒμ„±ν•˜λŠ” νŒ©ν† λ¦¬κ°€ μžˆλ‹€. κ°€λ Ή μ—¬κΈ°μ„œ λ³Έ 객체 λ¦¬ν„°λŸ΄μ„ presentationParameterFactory νŒ©ν† λ¦¬λ‘œ λ§Œλ“€μ–΄λ‚Ό 수 μžˆλ‹€.

이 μž₯의 νŒ©ν† λ¦¬λŠ” create ν•¨μˆ˜ ν•˜λ‚˜λ§Œ μžˆμ§€λ§Œ, μš©λ„μ— νŠΉν™”λœ μœ ν˜•λ³„ create λ©”μ„œλ“œκ°€ μ—¬λŸΏ μžˆλŠ” νŒ©ν† λ¦¬λ„ μžˆλ‹€.

νŒ©ν† λ¦¬λŠ” μ œν’ˆλ³„λ‘œ ν•„μš”ν•œ ν™˜κ²½μ„ ꡬ좕할 수 μžˆλŠ” νŽΈλ¦¬ν•œ 곳이닀. ν˜„μž₯μ—μ„œλŠ” λ‹¨μœ„ ν…ŒμŠ€νŠΈ, κΈ°λŠ₯ ν…ŒμŠ€νŠΈ, μ œν’ˆλ§ˆλ‹€ ν™˜κ²½μ΄ λ‹¬λΌμ§€λŠ” κ²½μš°κ°€ λ§Žμ€λ°, μ˜μ‘΄μ„±μ„ μ£Όμž…ν•˜λ©΄ 상황에 맞게 적절히 νŒ©ν† λ¦¬λ₯Ό μ μš©ν•  수 μžˆλ‹€.

끝으둜, νŒ©ν† λ¦¬λŠ” μ‹±κΈ€ν†€μœΌλ‘œλ„ λ°”κΏ” μ“Έ 수 μžˆμ–΄μ„œ 9μž₯μ—μ„œ μ„€λͺ…ν•œ μ—¬λŸ¬ 가지 싱글톀을 μ μš©ν•  수 μžˆλ‹€.

πŸ“š μ •λ¦¬ν•˜κΈ°β€‹

μ—°κ΄€λœ 객체 쀑 ν•˜λ‚˜λ₯Ό κΊΌλ‚΄ 생성할 수 μžˆλŠ” νŒ©ν† λ¦¬λ₯Ό μž‘μ„±ν–ˆλ‹€. νŒ©ν† λ¦¬μ—” λŒ€λΆ€λΆ„ create같은 μ΄λ¦„μ˜, ν•˜λ‚˜ λ˜λŠ” κ·Έ μ΄μƒμ˜ νŒŒλΌλ―Έν„°λ₯Ό λ°˜λ“  λ©”μ„œλ“œκ°€ ν•˜λ‚˜ μžˆλ‹€. 이 λ©”μ„œλ“œλŠ” 전달받은 νŒŒλΌλ―Έν„°λ₯Ό μ‚΄νŽ΄λ³΄κ³  μ•Œλ§žμ€ 객체λ₯Ό λ‚΄μ–΄μ€€λ‹€.

νŒ©ν† λ¦¬λŠ” 객체 생성을 κ°•λ ₯ν•˜κ²Œ λ‹€μŠ€λ¦¬κ³  ν•œ κ²Ή 더 μΆ”μƒν™”ν•œλ‹€.

νŒ©ν† λ¦¬ λ‹¨μœ„ ν…ŒμŠ€νŠΈμ—μ„œλŠ” λ‹€μŒμ„ ν™•μΈν•˜μž.

  • create ν•¨μˆ˜λŠ” 잘λͺ»λœ νŒŒλΌλ―Έν„°λ₯Ό 받지 μ•ŠλŠ”λ‹€.
  • νŒŒλΌλ―Έν„°κ°€ μ •μƒμ μœΌλ‘œ μ „λ‹¬λ˜λ©΄ 그에 λ”°λ₯Έ, μ›κ°μ²΄μ˜ 생성 ν•¨μˆ˜λ₯Ό μ •ν™•νžˆ ν˜ΈμΆœν•œλ‹€.
  • μ΄λ ‡κ²Œ ν•˜μ—¬ λ°˜ν™˜λœ 객체게 λ°”λ‘œ createκ°€ λ°˜ν™˜ν•œ 객체닀.