본문 바로가기
Javascript/Node.js

Factory Pattern - 생성자 디자인 패턴

by v8rus 2022. 2. 3.

팩토리

"특정 구현으로부터 객체의 생성을 분리"
가장 일반적인 디자인 패턴
팩토리는 함수일 뿐이라서 사용자에게 더 적은 유연성을 제공 (사용자 관점)
팩토리는 클로저를 활용하여 캡슐화를 강제하는데 사용 할 수 있음

 

 

1 객체 생성과 구현의 분리

JS는 단순성, 유용성, 작은 노출을 선호 => 함수형 방식 선호
객체 인스턴스 생성 시 실제로 new 연산자 / Object.create() 대신 팩토리 호출이 편하고 유연함

객체 생성과 구현을 분리
새 인스턴스의 생성을 감싸서 객체 생성시 더 많은 유연성과 제어를 제공 (프로그램 관점)
특정 조건에 따라 다른 유형의 객체를 반환할 수있음

클래스를 숨겨 확장이나 수정하는것을 막음

 

// 특정 유형의 객체에 바인딩
function createImage (name) {

  return new Image(name);
}
const image = new Image(name);

// 위의 예를 더 작은 클래스로 분할
function createImage(name) {
  if (name.match(/\.jpe?g$/)) {
  
    return new ImageJpeg(name);
  } else if (name.match(/\.gif$/)) {
  
    return new ImageGif(name);
  } ... else {
  
    throw new Error("Unsupported format!");
  }
}

 

 

2 캡슐화를 강제할 수있는 메커니즘

클로저로 캡슐화 메커니즘 사용 (팩토리)

* 캡슐화 : 외부 코드가 컴포넌트의 내부 핵심에 직접 접근하여 조작하는것을 방지하기 위해 접근을 제어하는 것

컴포넌트와의 상호작용은 오직 공용(public) 인터페이스를 통해서 가능
컴포넌트의 상세 구현은 변경으로부터 외부코드를 분리시킬수 있다.

* 객체지향의 기본 원칙 : 상속, 다형성, 추상화, 캡슐화
  캡술화 방법 : 스코프(scope) / 클로저 (clouser)
  팩토리는 쉽게 프라이빗(private) 변수를 강제 할 수 있음

 

function createPerson (name) {
  // privateProperties 객체 : 외부에서 접근 불가, person 객체가 제공하는 인터페이스만을 통해 접근 가능
  const privateProperties ={};
  
  // person 객체 : 팩토리가 반환하는 퍼블릭 인터페이스
  const person = {
    setName (name) {		// name 속성을 강제화, name이 person 객체의 일반 속성이었다면 강제화 불가
      if (!name) {
        throw new Error("A person must have a name");
      }
      privateProperties.name = name;
    },
    getName () {
      return privateProperties.name;
    }
  }

  person.setName(name);

  return person;
}

* 캡슐화 규약
  private 필드 맨 앞을 # 기호로 시작
  변수 앞에 _ 붙이기

 

 

3 간단한 코드 프로파일러 만들기

class Profiler {
  constructor (label) {
    this.label = label;
    this.lastTime = null;
  }

  start () {
    this.lastTime = process.hrtime();
  }

  end () {
    const diff = process.hrtime(this.lastTime);
    console.log(`Timer "${this.label}" took ${diff[0] seconds ` + `and  ${diff[1]} nanoseconds.`);
  }
}

프로덕션의 경우 위의 프로파일러를 비활성
이를 위해서 new 연산자를 사용해 Profile 객체를 직접 인스턴스화 하기 위해 다른 로직으로 분기
또는 팩토리 사용, 객체 생성을 추상화하고 모드별로 분기

 

const nonPrifiler = {
  start () { ... },
  end () { ... }
}

// createProfile()은 팩토리 함수, profiler 객체의 생성을 추상화함
export function createProfiler (label) {
  if (process.env.NODE_ENV === "production") {
    return nonPrifiler;
  }

  return new Profiler(label);
}

동적 타이핑으로 상황따라 객체를 선택

* 덕타이핑 : 어떤 상황에서 new 연산자로 인스턴스화된 객체를, 다른 상황에서는 간단한 객체 리터럴을 반환

원하는 방식으로 객체를 생성하는 방법을 보여줌
추가적인 초기화 단계를 실행하거나 특정 조건에 따라 다른 유형의 객체를 반환 할 수있음
이  모든 작업은 세부 사항들로부터 사용자를 분리함으로써 가능


 

최종 실행 예제를 봅시다.

// index.js
import {createProfiler } from './profiler.js';

function getAllFactors (intNumber) {
  const profiler = createProfiler(`Finding all Factors of ${intNumber}`);

  profiler.start();
  const factors=[];
  for (let factor = 2; factor <= intNumber; factor++) {
    while ((intNumber % factor) === 0) {
      factors.push(factor);
      intNumber = intNumber /factor;
    }
  }
  profiler.end();

  return factors;
}

const myNumber = process.argv[2];
const myFactors = getAllFactors(myNumber);		// 환경 변수 기반으로 생성되는 Profiler 객체
console.log(`Faoctrs of ${MyNumber} are : `, myFactors);

 

 

Summary

팩토리는 매우 일반적이 패턴이오.

Ex) Knex의 SQL 쿼리 빌더 - 단순히 팩토리 함수 하나만을 제공

  팩토리로 다양한 검사를 수행, 데이터베이스 엔진에 맞는 dialect 객체를 선택하고 Knex 객체를 생성하여 반환

댓글