-
자바스크립트 디자인 패턴(1)카테고리 없음 2021. 9. 26. 10:33728x90
전통적인 디자인 패턴을 자바스크립트로 작성하면서 문제를 해결하는 방식에 대해 알아보겠습니다.- Factory 패턴
- 공장에서 찍어내듯이 인스턴스 생성 과정을 추상화 하는 것
- 왜? 개체 생성과 구현 분리함으로 유연성 및 유지보수성 증대 및 클래스를 비공개로 유지
- 클로져를 활용하여 Private 변수를 선언하는 방법으로 활용
// new 연산자를 활용한 개체 생성 const dinner = new Dinner("PIZZA"); // Factory 패턴 => 유지보수성 🚀 const makeDinner = (food, _ = typeCheck(food, "string")) => { if (food == "PIZZA") return new Pizza(food); else if (food == "CHICKEN") return new Chicken(food); else if (food == "SALAD") return new Salad(food); else if (food == "KEBAB") return new Kebab(food); else { throw new Error("We dont have recipes!!"); } };
// Knex 패키지 (https://github.com/knex/knex/blob/faacfd726ea4c6ff809758edf88c2c0154af7717/lib/knex.js#L12) // Factory 패턴을 통해 다양한 검사를 진행하고 필요한 개체 생성하여 리턴 function Knex(config) { let Dialect; if ( arguments.length === 0 || (!config.client && !config.dialect)) { Dialect = Client; } else if ( typeof config.client === "function" && config.client.prototype instanceof Client ) { Dialect = config.client; } else { const clientName = config.client || config.dialect; if (!SUPPORTED_CLIENTS.includes(clientName)) { throw new Error( `knex: Unknown configuration option 'client' value ${clientName}. Note that it is case-sensitive, check documentation for supported values.` ); } const resolvedClientName = resolveClientNameWithAliases(clientName); Dialect = require(`./dialects/${resolvedClientName}/index.js`); } if (typeof config.connection === "string") { config = Object.assign({}, config, { connection: parseConnection(config.connection).connection, }); } const newKnex = makeKnex(new Dialect(config)); if (config.userParams) { newKnex.userParams = config.userParams; } return newKnex; }
- Builder 패턴
- Helper 함수(post, send 등)를 활용해서 인자들을 모으고 마지막에 인스턴스를 생성하는 패턴
- 왜? 복잡한 개체 생성을 단순화
// superagent 패키지 (https://www.npmjs.com/package/superagent) superagent .post('/api/pet') .send({ name: 'Manny', species: 'cat' }) // sends a JSON post body .set('X-API-Key', 'foobar') .set('accept', 'json') .end((err, res) => { // Calling the end function will send the request });
- Revealing Constructor 패턴
- 객체가 생성된 순간에만 필요한 객체의 내부기능을 노출하도록 하는 패턴 (예시 Promise)
- 왜? 강력한 캡슐화가 제공 가능
- 전통적인 디자인패턴에 존재하지 않음 => JS 커뮤니티에서 새롭게 언급
// resolve, reject의 실행자를 입력받아 Promise 내부를 초기화/변경을 진행 // 초기화 및 변경 이후에는 Promise 흐름제어 불가 new Promise((res, rej) => { //... });
- SingleTon 패턴
- 클래스의 인스턴스 생성을 하나로 강요하여 접근을 중앙 집중화하도록 하는 패턴
- 왜? 상태 정보 공유 및 리소스 최적화
- JS 진영에서는 모듈시스템으로 의미없는 패턴 => 전체 경로를 키로 모듈캐싱이 진행되어 불러온 패키지가 싱글톤이라는 것을 보장
- 문제점 👻 : 복잡한 상황에 맞추어 동작하는 시스템을 구현에 적합하지 않음 - Dependency Injection 패턴
- 앞서의 복잡한 상황에서 싱글톤의 문제를 해결하는 방식으로 종속성을 주입하는 방식으로 해결 가능
- 문제점은 사람이 종속성을 쉽게 파악하기 어려워 실제로는 프로그래밍이 더욱 어려워짐 - Proxy 패턴
- 다른 개체에 대한 액세스를 제어하여 동작을 보안하거나 추가적인 제어를 실행하는 패턴
- 왜? 데이터 보안 및 검증 등 추가적인 제어를 손쉽게 구성 가능
- 컴포지션 및 팩토리 함수 / 몽키패치 / ES6 Proxy 개체를 기반으로 구현 가능
class Stack { #store; constructor() { this.#store = []; } push(data) { this.#store.push(data); } pop() { this.#store.pop(); } show() { return this.#store.join(); } } // 컴포지션을 활용한 프록시 구현, 비슷한 방식으로 팩토리 함수를 활용해서도 구현가능 class SaftyStack { #stack; constructor(stack) { this.#stack = stack; } push(data) { this.#stack.push(data); // 단순 위임 } pop() { if (this.#stack.show().length === 0) // 추가 제어 로직 throw Error("stack is empty"); this.#stack.pop(); } show() { this.#stack.show(); // 단순 위임 } }
// ES6의 Proxy 개체를 활용한 방법 const safeStackProxyHandler = { get: (target, property) => { if (property === "pop") { return () => { if (target.show().length === 0) throw Error("stack is empty [PROXY]"); return target.pop(); }; } return target[property]; }, }; const _sfyStackWithProxy = new Proxy(_stack, safeStackProxyHandler); _sfyStackWithProxy.pop();
- Decorator 패턴
- 기존 개체의 인스턴스에 새로운 동작을 추가하는 패턴(상속X)
- 구현전략은 프록시 패턴과 동일하며 서로 상호 교환 가능 - Adaptor 패턴
- 기존 개체의 인터페이스를 다른 형태로 변경하여 제공하는 패턴
- 마찬가지로 구현전략은 프론시 및 데코레이터와 비슷
2.
- Factory 패턴