zero-wiki Help

07장-자바스크립트-디자인-패턴

7.1 생성 패턴

생성 패턴은 객체를 생성하는 방법을 다룹니다.

7.2 생성자 패턴

생성자는 객체가 새로 만들어진 뒤 초기화하는 데에 사용되는 특별한 메서드입니다. ES2015 이후로 생성자를 가진 클래스를 만들 수 있습니다.

7.2.1 객체 생성

// 객체를 만드는 일반적인 세가지 방법 const obj1 = {}; const obj2 = Object.create(Object.prototype); const obj3 = new Object();

7.2.2 생성자의 기본 특징

클래스는 새 객체를 초기화 하는 constructor라는 이름의 메서드를 가지고 있어야 하며, 또한 new 키워드를 사용하여 생성자를 호출할 수 있습니다.

class Animal { constructor(type, gender){ this.type = type; this.gender = gender; } toString(){ return `type: ${this.type}, gender: ${this.gender}`; } }

7.2.3 프로토타입을 가진 생성자

프로토타입 객체는 함수나 클래스 등 특정 객체의 모든 인스턴스 내에 공통 메서드를 쉽게 정의할 수 있습니다.

Animal.prototype.eat = function (food){ console.log(`${food}를 먹습니다.`) }

7.3 모듈 패턴

모듈은 애플리케이션 아키텍처의 핵심 구성 요소이며, 코드 단위를 체계적으로 분리하는데 효과적으로 활용됩니다.

ES2015 이전에는 CommonJS 모듈이나 AMD 모듈이 주로 사용되었습니다.

7.3.1 객체 리터럴

객체 리터럴 표기법은 중괄호 안에서 키와 값을 쉼표로 구분하여 작성하여 객체를 정의하는 방법입니다.

const user = { name: "zero", age: 20 }

객체 리터럴은 선언시 new 연산자가 필요하지 않습니다.

7.3.2 모듈 패턴

클래스의 캡슐화를 위해 처음 고안되었습니다.

비공개

모듈 패턴은 클로저를 활용하여 '비공개' 상태와 구성을 캡슐화 합니다. ES2019(ES10) 이전의 자바스크립트에서는 접근 제한자 (#, 해시)를 제공하지 않아 '비공개'라는 개념이 존재하지 않았습니다. 때문에 함수 스코프를 이용하여 비공개 개념을 구현하였습니다.

반환된 객체에 포함된 변수를 비공개 하려면 WeakMap을 사용할 수 있습니다.

class Counter { constructor(){ this.count = 0; } increment(){ this.count++; } getCount(){ return this.count; } }

7.3.4 WeakMap을 사용하는 최신 모듈 패턴

WeakMap 객체는 기본적으로 키가 약하게 유지되는 맵입니다. 그 말은 참조되지 않는 키는 가비지 컬렉션의 대상이 됩니다.

let _counter = new WeakMap(); class Counter { constructor(){ _counter.set(this, 0); } increment(){ let counter = _counter.get(this); counter++; _counter.set(this, counter); return _counter.get(this); } reset(){ _counter.set(this, 0) } }

7.3.5 최신 라이브러리와 모듈

리액트에서도 다음과 같이 각각의 파일로 나누어 모듈을 관리할 수 있습니다.

const style = { root: { borderRadius: 3, border: 0, color: 'white', margin: '0 20px' }, primary: { background: 'black' }, secondary: { background: 'green' } } export default function CustomButton(props) { return ( <Button {...props} style={{ ...style.root, ...style[props.color]}}> {props.children} </Button> ) }

7.4 노출 모듈 패턴

공개 변수나 메서드에 접근하기 위해 가져온 메인 객체의 이름을 반복해서 사용해야 한다는 답답함을 느끼면서 생겨났습니다.

모든 함수와 변수를 비공개 스코프에 정의하고, 공개하고 싶은 부분만 포인터를 통해 비공개 요소에 접근하게 해주는 익명 객체를 반환하는 패턴이 탄생하였습니다.

let privateVar = 'Zero1016' const publicVar = 'Eden' const privateFunction = () => { console.log(`Name: ${privateVar}`); } const publicSetName = (strName) => { privateVar = publicVar; } const publicGetName = () => { privateFunction(); } // 비공개 함수와 속성에 접근하는 공개 포인터 const myRevalingModule = { setName: publicSetName, greeting: publicVar, getname: publicGetName, }; export default myRevalingModule;

노출 모듈 패턴의 경우 참조하는 함수를 수정할 수 없습니다. 비공개 함수가 비공개 구현을 참조하기 때문에 발생합니다. 비공개 구현 함수를 직접적으로 수정하지 않는 이상 함수를 수정할 수 없습니다.

7.5 싱글톤 패턴

클래스의 인스턴스가 오직 하나만 존재하도록 제한하는 패턴입니다. 이 패턴은 전역에서 접근 및 공유해야 하는 단 하나의 객체가 필요할 때 유용합니다.

싱글톤 패턴은 정적 클래스나 객체와는 다르게 초기화를 지연시킬 수 있습니다. 초기화 시점에 필요한 특정 정보가 유효하지 않을 수도 있기 때문입니다.

let instance; const privateMethod = () => { console.log("I am private"); } class Singleton { constructor(){ if(!instance) { this.publicProperty = 'I am public'; instance = this; } return instance; } } export default Singleton()

싱글톤과 정적 클래스 사이의 차이점을 명확히 아는 것은 중요합니다. 정적 인스턴스로 구현했다고 하더라도, 필요할 때까지는 리소스나 메모리를 소모하지 않도록 지연 생성할 수도 있습니다.

싱글톤은 유용한 패턴입니다. 다만 자바스크립트에서 싱글톤이 필요하다는 것은 설계를 다시 생각해 봐야 한다는 신호일 수도 있습니다. C++이나 자바와 달리 자바스크립트는 객체를 직접적으로 생성할 수 있습니다.

따라서 싱글톤 클래스를 만드는 대신에 직접 객체 하나를 생성할 수도 있습니다.

자바스크립트에서 싱글톤을 사용할 경우 다음과 같은 단점이 있습니다.

  1. 싱글톤임을 파악하는 것이 힘들다.

  2. 테스트하기 힘들다.

  3. 신중한 조정이 필요하다.

7.5.1 리액트의 상태관리

리액트를 통해 웹 개발을 한다면 싱글톤 대신 Context API나 리덕스 같은 전역 상태 관리 도구를 이용하여 개발할 수도 있습니다. 싱글톤과 달리, 이러한 전역 상태 관리 도구는 변경 불가능한 읽기 저용 상태를 제공합니다.

7.6 프로토타입 패턴

자바스크립트 생태계에서는 클래스처럼 따로 정의되는 것이 아니라, 이미 존재하는 다른 객체를 복재하여 새로운 객체를 만들어냅니다.

프로토타입 패턴의 장점은 다른 언어의 기능을 따라하지 않고, 자바스크립트만이 가진 고유의 방식으로 작업할 수 있다는 것입니다.

7.7 팩토리 패턴

팩토리 패턴은 객체를 생성하는 생성 패턴의 하나입니다. 다른 패턴과 달리 생성자가 필요로하지 않지만, 필요한 타입의 팩토리 객체를 생성하는 다른 방법을 제공합니다.

class Animal { constructor({name, color}) { this.name = name; this.color = color; } toString() { return `Animal name is ${this.name}, color is ${this.color}`; } } // 팩토리 클래스 class AnimalFactory { createAnimal(type, props) { switch(type) { case 'dog': return new Animal({...props, type: 'dog'}); case 'cat': return new Animal({...props, type: 'cat'}); default: throw new Error('지원하지 않는 동물 타입입니다.'); } } } // 사용 예시 const factory = new AnimalFactory(); const dog = factory.createAnimal('dog', { name: '멍멍이', color: '갈색' }); const cat = factory.createAnimal('cat', { name: '야옹이', color: '흰색' });

7.7.1 팩토리 패턴을 사용하면 좋은 상황

팩토리 패턴은 다음과 같은 상황에서 특히 유용합니다.

  1. 객체 생성 과정이 복잡할 때

  2. 동적인 요소나 애플리케이션 구조에 깊게 의존하는 경우

  3. 객체 생성에 필요한 설정이나 의존성이 많은 경우

  4. 객체 생성 로직을 한 곳에서 관리하고 싶을 때

7.7.2 팩토리 패턴을 사용하면 안 되는 상황

잘못된 상황에 팩토리 패턴을 사용하면 애플리케이션의 복잡도가 크게 증가할 수 있습니다. 라이브러리나 프레임워크를 설계 목표가 아니라면 생성자를 사용하는 것이 좋습니다.

7.7.3 추상 팩토리 패턴

같은 목표를 가진 각각의 팩토리들을 하나의 그룹으로 캡슐화하는 패턴입니다. 객체가 어떻게 생성되는지에 대해 알 필요 없이 객체를 사용할 수 있게 해줍니다.

// 추상 팩토리 인터페이스 class GUIFactory { createButton() { throw new Error('createButton() must be implemented'); } createCheckbox() { throw new Error('createCheckbox() must be implemented'); } } // Windows 스타일 팩토리 class WindowsFactory extends GUIFactory { createButton() { return new WindowsButton(); } createCheckbox() { return new WindowsCheckbox(); } } // Mac 스타일 팩토리 class MacFactory extends GUIFactory { createButton() { return new MacButton(); } createCheckbox() { return new MacCheckbox(); } } // 클라이언트 코드 class Application { constructor(factory) { this.factory = factory; } createUI() { const button = this.factory.createButton(); const checkbox = this.factory.createCheckbox(); return { button, checkbox }; } } // 사용 예시 const windowsApp = new Application(new WindowsFactory()); const macApp = new Application(new MacFactory());

7.8 구조 패턴

구조 패턴은 클래스와 객체의 구성을 다룹니다. 상속의 개념을 통해 인터페이스와 객체를 구성하여 새로운 기능을 추가할 수 있는 것처럼 말입니다.

7.9 퍼사드 패턴

퍼사드란 실제 모습을 숨기고 꾸며낸 겉모습만을 세상에 드러내는 것을 뜻합니다. 여기서 퍼사드 패턴이란 심층적인 복잡성을 숨기고, 사용하기 편리한 높은 수준의 인터페이스를 제공하는 패턴입니다.

jQuery와 같은 자바스크립트 라이브러리에서 흔히 볼 수 있는 패턴입니다.

// jQuery에서 퍼사드 패턴을 사용하는 예제 $(el).css $(el).animate()

7.10 믹스인 패턴

C++나 Lisp 같은 전통적인 프로그래밍 언어에서 믹스인은 서브클래스가 상속받아 기능을 재사용 할 수 있도록 하는 클래스입니다.

7.11 서브클래싱

부모클래스를 확장하는 자식 클래스를 서브 클래스라고 합니다.

서브클래싱이란 부모 클래스 객체에서 속성을 상속받아 새로운 객체를 만드는 것을 말합니다.

class SuperCar extends Car { constructor(name, power){ super(name); this.power = power } } const superCar = new SuperCar('람보르기니', 200); console.log(superCar); // power를 가지고 있는 Car를 호출합니다.

7.12 믹스인

믹스인은 최소한의 복잡성으로 객체의 기능을 빌리거나 상속할 수 있게 해줍니다. 다른 여러 클래스를 아울러 쉽게 공유할 수 있는 속성과 메서드를 가진 클래스입니다.

const MyMixins = superClass => { return class extends superClass { moveUp(){ console.log('move up'); } moveDown(){ console.log('move down'); } } }

함수의 중복을 줄이고 재사용성을 높이는 믹스인 패턴이지만 논쟁의 여지가 남아있습니다. 클래스나 객체의 프로토타입에 기능을 주입하는 것을 나쁘게 여기기도 합니다. 프로토타입의 오염과 함수의 출처에 대한 불확실성을 초래하기 때문입니다.

7.13 데코레이터 패턴

코드 재사용을 목표로 하는 구조 패턴입니다. 믹스인과 마찬가지로 서브클래싱의 다른 방법입니다. 이는 기존 클래스에 동적으로 기능을 추가하기 위해서 사용됩니다.

class Car { constructor() { this.power = 100; } run(){ this.power -= 10; } getPower(){ return this.power; } } class SuperCar extends Car { constructor(car) { super(); this.car = car; } getPower(){ return this.car.getPower() - 20; } }

7.14 의사 클래스 데코레이터

7.14.1 인터페이스

인터페이스란 객체가 가져야 할 메서드를 정의하는 방법입니다.

인터페이스는 스스로 문서의 역할을 하고, 재사용성을 높이기 때문에 변경사항이 객체에도 전달되면서 코드의 안정성을 높이는 역할을 합니다.

7.16 플라이웨이트 패턴

반복되고 느리고 비효율적으로 데이터를 공유하는 코드를 최적화하는 구조적 해결 방법입니다.

7.16.1 사용법

플라이웨이트 패턴을 사용하는 방법으로는 데이터 레이어에서 메모리에 저장된 수많은 비슷한 객체 사이로 데이터를 공유하는 방법이 있습니다.

7.16.2 데이터 공유

플라이 웨이트 패턴에는 두 가지 개념이 있습니다. 내재적 상태와 외재적 상태로, 내재적 정보는 객체의 내부 메서드에 피룡한 것이며, 없으면 동작하지 않습니다. 반면에 외재적 정보는 제거되어 외부에 저장할 수 있습니다.

7.16.3 전통적인 플라이웨이트 구현 방법

다음과 같은 특징들을 가지고 있습니다.

  • 플라이웨이트: 외부의 상태를 받아 작동할 수 있게 하는 인터페이스입니다.

  • 구체적 플라이웨이트: 인터페이슬르 실제로 구현하고 내부 상태를 저장합니다. 또한 다양한 컨텍스트 사이에서 공유될 수 있어야 하며, 외부 상태를 조작할 수 있어야 합니다.

  • 플라이웨이트 팩토리: 플라이 웨이트 객체를 생성 및 관리합니다. 객체가 생성되어 있다면 해당 객체를 반환하고 없다면 새 객체를 그룹에 추가한 뒤 반환합니다.

7.17 행위 패턴

객체 간의 의사소통을 돕는 패턴입니다. 시스템 내 서로 다른 객체 간의 의사소통 방식을 개선하고 간소화하는 것을 목적으로 합니다.

7.18 관찰자 패턴

한 객체가 변경될 때 다른 객체들에게 변경되었음을 알릴 수 있게 해주는 패턴입니다.

const events = (function (){ const topics = {}; const _hasOwnProperty = topics.hasOwnProperty; return { subscribe: function (topic, listener) { if(!_hasOwnProperty.call(topics, topic)) topics[topic] = []; const index = topics[topic].push(listener) - 1; return { remove: function (){ delete topics[topic][index]; }, }; }, publish: function (topic, info) { if(!_hasOwnProperty.call(topics, topic)) return; topics[topic].forEach(function (item){ item(info !== undefined ? info : {}); }) } } })() let mailCounter = 0; const subscriber1 = events.subscribe('inbox/newMessage', (data) => { document.querySelector('.messageSender').innerHTML = data.sender; document.querySelector('.messagePreview').innerHTML = data.body; }) const subscriber2 = events.subscribe('inbox/newMessage', (data) => { document.querySelector('.newMessageCounter').innerHTML = ++mailCounter; }) events.publish('inbox/newMessage', { sender: 'hello@google.com', body: 'Hey there!' })

7.18.2 장점

각각의 요소들이 직접 연결되어 있는 곳을 파악하고, 주체와 관찰자의 관계로 대체할 수 있는 부분을 찾아낼 수 있도록 도움을 줍니다. 이를 통해 애플리케이션을 작고 느슨하게 변경할 수 있으며, 코드의 관리와 재사용성을 높일 수 있습니다.

7.18.3 단점

구독자와 발행자 사이의 관계가 동적으로 결정되기 때문에 어떤 구독자가 어떤 발행자에 의존하는지 추적하기 어려울 수 있습니다.

7.19 중재자 패턴

하나의 객체가 이벤트 발생 시 다른 여러 객체들에게 알림을 보낼 수 있는 디자인 패턴입니다. 이 패턴은 하나의 객체가 다른 객체에서 발생한 특정 유형의 이벤트에 대해 알림을 받을 수 있다는 점이 있습니다.

Last modified: 01 April 2025