[JS] 생성자 디자인(Singleton, Factory, 공개생성자)

Vorhandenheit ·2022년 9월 24일
0

JS/Node 

목록 보기
60/63

1. Factory

어떤 함수가 객체를 반환할 때 이 함수를 팩토리 함수라고 부릅니다.

function createImage (name) {
  if (name.match(/\.jpe?g$)) {
    return new ImageJpeg(name)
  }
  else if (name.match(/\.git$/)) {
    return new ImageGitf(name)
  }
  else if (name.match(/\.png$/)) {
    return new ImageBitmapRenderingContext(name)
  }
  else {
    throw new Error('unsupported Format')
  }
}

new를 사용하면 코드를 특정 유형의 객체에 바인딩합니다.
팩토리를 사용함으로 클래스를 비공개로 유지할 수 있음

(1) 캡슐화를 강제

function createPerson(name) {
	const privateProperties = {}
    const person = {
    	setName(name) {
        	if(!name) {
            	throw new Error('not name')
            }
          privateProperties.name= name
        },
      getName() {
      	return privateProperties.name
      }
    }
    person.setName(name)
  return person
}

person 객체가 제공하는 인터페이스만을 통해 privateProperties 접근할 수 있습니다.

클로저를 사용해서 두 개의 객체를 생성하였습니다.
하나는 팩토리가 반환하는 퍼블릭 인터페이스인 person 객체이고
다른 하나는 외부에서 접근할 수 없고, person객체가 제공하는 인터페이스만을 통해 접근할 수 있는 privateProperties입니다.

(2) 간단한 코드 프로파일러

// start가 호출될 때 현재 시간을 저장한 다음 end()가 실행될 떄 경과 시간을 계산하여 콘솔에 출력
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`)
  }
}

const profiler = profile()
profiler.start()
profiler.end()

2. Builder

객체를 만들어 사용하다보면 객체 생성자가 너무 길어져 복잡해지는 경우가 생기는데 그럴 때 빌더 패턴은 객체의 생성 과정을 분리해 보기 쉽게 만듭니다.
복잡한 객체의 생성을 단순화하는 생성 디자인 패턴으로 단계별로 객체를 만들 수 있습니다.


class Boat {
	constructor (hasMotor, motorCount, motorBand, motorModel, hasSails, sailsCount, sailsMaterial, sailsColor) {
    }
}

const myBoat = new Boat(true, 2, 'Best Motor', 'OM123', true, 1, 'fabric', 'white')

=> 개선
모든 객체를 하나의 리터럴에 모음

class Boat {
	constructor (allParameters) {
    }
}
const myBoat = new Boat({
	hasMotor : true,
  motorCount : 2,
  motorBrand : 'Best Motor',
  motorModel : 'OM123',
  hasSails : true,
  sailsCount : 1
  sailsMaterial : 'fabric',
 ...
})

=> 빌더 패턴

class BoatBuilder {
	withMotors (count, brand, model) {
    	this.hasMotor = true
      this.motorCount = count
      this.motorBrand = brand
      this.motorModel = model
      return this
    }
  withSail (count, material, color) {
  	this.hasSails = true
    this.sailsCount = count
    this.sailsMaterial = material
    this.sailColor = color
    return this
  }
  hullColor (color) {
  	this.hullColor = color
    return this
  }
  withCabin() {
  	this.hasCabin = true
    return this
  }
}

build() {
	return new Boat({
    	hasMotor: this.hasMotor,
     motorCount: this.motorCount
      ...
    })
}

const myBoat = new BoatBuilder()
.withMotors(2, 'BestMotor', 'OM123')
.withSails(1, 'fabirc', 'white')
...
  • 복잡한 생성자를 더 읽기 쉽고 관리하기 쉬운 여러 단계로 나누는 것
  • 한번에 관련된 여러 매개 변수들을 설정할 수 있는 빌더 함수를 만듬

(1) url 객체 빌더 구현하기

export class Url {
	constructor (protocol, username, password, hostname, port, pathname, search, hash) {
    	this.protoco, = protocol
      this.username = username
      this.password = password
      this.hostname= hostname
      this.port = port
      this.pathname = pathname
      this.search = search
      this.hash = hash
      
      this.validate()
    }
  validate() {
  	if (!this.protocol || this.hostname) {
    	throw new Error('not protocol, hostname')
    }
  }
  toString() {
  	let url = ''
    url += `${this.protocol}`
    if (this.username && this.password) {
    	url += `${this.username}:${this.password}@`
    }
    url += this.hostname
    ...
    return url
  }
}

return new Url('https', null, null, 'example.com', null, null...)

=> builder

export class UrlBuiler {
	setProtocol (protocol) {
    	this.protocol = protocol
      return this
    }
  setHostname(hostname) {
  	this.hostname = hostname
    return this
  }
  setPort(port) {
  	this.port = port
    return this
  }
  ...
  build () {
  	return new Url(this.protocol, this.username, this password, this.hostname...)
  }
}

(2) 실전

superagent는 빌더 패턴을 구현해 새로운 요청의 생성을 단순화하는 걸 목표로합니다.

superagent
.post('https://~~~')
.send({name : 'ccm', job : 'development'}})
.set('accept', 'json')
.then((response) => {
	//
})

3. 공개 생성자

객체가 생성되는 순간에만 객체의 내부적 기능의 일부를 노출시킬 수 있을까?

  • 생성시에만 수정할 수 있는 객체의 생성
  • 생성시에만 사용자 정의 동작을 정의할 수 있는 객체 생성
  • 생성시 한 번만 초기화할 수 있는 객체 생성

(1) 변경불가능한 버퍼 만들기

const MODIFIER_NAMES = ['swap', 'write', 'fill']

export class ImmutableBuffer {
	constructor (size, executor) {
    	const buffer = Buffer.alloc(size)
        const modifiers = { }
        for (const prop in buffer) {
        	if (typeof buffer[prop] !== 'function') {
            	continue
            }
         	if (MODIFIER_NAMES.some(m => prop.startsWith(m))) {
            	modifiers[prop] = buffer[prop].bind(buffer)
            }
          	else {
            	this[prop] = buffer[prop].bind(buffer)
            }
        }
      excutor(modifers)
    }
}
  1. 먼저 생성자의 인자에 지정된 크기의 새로운 Node.js 버퍼를 할당
  2. buffer를 변경할 수 있는 함수들을 보관한느 객체 리터럴을 만듬
  3. buffer 내부에 모든 속성들을 차례대로 살펴보면서 함수가 아닌 속성을 건너뜀
  4. 속성이 함수이면서 MODIFIER_NAMES 배열에 있는 이름 중 하나인지 살펴봄으로 현재의 속성이 버퍼를 수정할 수 있는 함수인지 식별, 맞다면 바인드 한 후에 modifiers 객체에 추가
  5. 함수가 modifier 함수가 아니면 현재 인스턴스에 직접 추가
  6. 생성자에서 입력으로 받은 실행 함수를 호출하면서 인자로 modifier객체를 전달할려면 실행 함수가 내부 buffer를 변경할 수 있습니다.

2. 실전

완벽한 캡슐화를 제공해야하는 경우에 사용,대표적인게 node.js promise

(1) 객체 생성과 구현의 분리

팩토리는 새 인스턴스 생성을 감싸서 객체 생성시에 더 많은 유연성과 제어를 제공

4. 싱글톤 패턴

(1) 싱글톤 패턴

싱글톤이란 전체 시스템에서 하나의 인스턴스만 존재하도록 보장하는 객체 생성패턴입니다.

인스턴스를 하나만 생성하기 때문에, 특정 클래스에서 인스턴스를 생성할 때 처음 호출 시에는 객체를 생성하지만 두 번째부터는 생성한 객체를 반환합니다.

(1) 클로저

let Animal = () => {
	const animal = this;
  	Animal = () => {
    	return animal
    }
}

위 방법은 처음 호출되었을 때 animal = this라는 클로저를 선언합니다. 두 번째 호출되었을시 클로저 함수를 반환합니다.

(2) 즉시 실행 함수

const Animal = (() => {
	return {
    	run() {
        }
    }
})()

Animal 함수는 run 메소드를 가지는 객체를 반환하여 할당합니다.

let singleTon = function() {
  	let instance; // 비공개 변수 정의
  
  function secret() { // 비공개 메서드 정의
  	if (instance) {
    	return instance;
    }
    instance = this;
    //Singleton initialization code
  }
  singleTon.getInstance = function() {
  	return instance || new Singleton()
  }
  return singleTon;
}());

let first = singleTon.getInstance();
let second = singleTon.getInstance();

(3) 내부 클래스

const Animal = (()=>{
    let instance = null;
    const InnerClass = function () {
        
    }

    InnerClass.prototype.run = function () { };

    let privateMember1 = function () { };

    return {
        getInstance () {
            if (instance === null) {
                instance = new InnerClass();
            }
            return instance;
        }
    };
})();

const o1 = Animal.getInstance();
const o2 = Animal.getInstance();

(4) class

class singleTon {
  constructor() {
  	if (instance) {
    	return instance;
      instance = this;
    }
  }
}
  const bar = new singleTon();
  const qaz = new singleTon()

=> 정적 필드 기능 추가

class singleTon {
	static instance
  constructor() {
  	if (instance) return instance;
    instance = this
  }
}

const first = new singleTon();
const second = new singleTon();

(5) 필요성

싱글톤 패턴을 찾다보니, 싱글톤 패턴은 node.js에서 필요없다는 말이 있었습니다.
왜냐하면 node.js 에서는 require한 모듈은 require.cache에 저장되어 매번 새로운 인스턴가 생성되는게 아니라 캐싱된 객체 인스턴스를 재사용하기 떄문입니다.

그래서 싱글톤 패턴으로 작성할 경우, 코드가 더 복잡해 질 수 있습니다.


하지만 코드 디자인 측면에서 장점이 있다면 사용하는게 좋습니다. (메모리 효율이 않좋더라도)

profile
읽고 기록하고 고민하고 사용하고 개발하자!

0개의 댓글