어떤 함수가 객체를 반환할 때 이 함수를 팩토리 함수라고 부릅니다.
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를 사용하면 코드를 특정 유형의 객체에 바인딩합니다.
팩토리를 사용함으로 클래스를 비공개로 유지할 수 있음
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입니다.
// 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()
객체를 만들어 사용하다보면 객체 생성자가 너무 길어져 복잡해지는 경우가 생기는데 그럴 때 빌더 패턴은 객체의 생성 과정을 분리해 보기 쉽게 만듭니다.
복잡한 객체의 생성을 단순화하는 생성 디자인 패턴으로 단계별로 객체를 만들 수 있습니다.
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')
...
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...)
}
}
superagent는 빌더 패턴을 구현해 새로운 요청의 생성을 단순화하는 걸 목표로합니다.
superagent
.post('https://~~~')
.send({name : 'ccm', job : 'development'}})
.set('accept', 'json')
.then((response) => {
//
})
객체가 생성되는 순간에만 객체의 내부적 기능의 일부를 노출시킬 수 있을까?
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)
}
}
완벽한 캡슐화를 제공해야하는 경우에 사용,대표적인게 node.js promise
팩토리는 새 인스턴스 생성을 감싸서 객체 생성시에 더 많은 유연성과 제어를 제공
싱글톤이란 전체 시스템에서 하나의 인스턴스만 존재하도록 보장하는 객체 생성패턴입니다.
인스턴스를 하나만 생성하기 때문에, 특정 클래스에서 인스턴스를 생성할 때 처음 호출 시에는 객체를 생성하지만 두 번째부터는 생성한 객체를 반환합니다.
let Animal = () => {
const animal = this;
Animal = () => {
return animal
}
}
위 방법은 처음 호출되었을 때 animal = this
라는 클로저를 선언합니다. 두 번째 호출되었을시 클로저 함수를 반환합니다.
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();
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();
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();
싱글톤 패턴을 찾다보니, 싱글톤 패턴은 node.js에서 필요없다는 말이 있었습니다.
왜냐하면 node.js 에서는 require한 모듈은 require.cache에 저장되어 매번 새로운 인스턴가 생성되는게 아니라 캐싱된 객체 인스턴스를 재사용하기 떄문입니다.
그래서 싱글톤 패턴으로 작성할 경우, 코드가 더 복잡해 질 수 있습니다.
하지만 코드 디자인 측면에서 장점이 있다면 사용하는게 좋습니다. (메모리 효율이 않좋더라도)