Banner Made by Banner Maker
소프트웨어 디자인 패턴 중 팩토리 메서드 패턴(Factory Method Pattern)에 대해서 알아보겠습니다.
먼저 이 패턴에 팩토리(Factory) 라는 이름이 붙은 이유는 공장에서 상품을 생산하듯이, 팩토리 메서드로 비슷한 객체를 찍어내는 동작을 하기 때문입니다.
잠시 Java와 같은 객체 지향 언어 관점에서 살펴보겠습니다.
팩토리 패턴 (Factory Pattern)이 아닌 팩토리 메서드 패턴(Factory Method Pattern) 이라고 하는 이유는 이 패턴이 템플릿 메서드 패턴(Template Method Pattern)에서 파생한 것이기 때문입니다.
템플릿 메서드 패턴이란, 상속 관계에 있는 두 클래스에서 상위 클래스가 중요한 뼈대를 결정하고, 하위 클래스에서는 추상 메서드를 통해 구체적인 내용을 결정하도록 하는 디자인 패턴입니다.
이런 구조로 만들게 되면 상위 클래스에서 로직을 공통화할 수 있다는 장점이 있습니다. 이러한 템플릿 메소드 패턴을 인스턴스 생성에 응용한 것이 팩토리 메서드 패턴입니다.
하지만 JavaScript는 프로타입 기반 언어로, 원래는 Java나 C++ 에서 사용하는 클래스라는 개념이 없습니다. 대신 프로토타입을 이용해 상속을 구현하거나, 혹은 new
키워드와 함수를 이용해 클래스처럼 동작하도록 흉내낼 수 있습니다.
(c.f. ES6에서 추가된 class
는 문법적 설탕일 뿐 내부적으로는 ES5 이하에서 prototype을 이용해 구현한 것과 같습니다.)
function Person(name) {
this.name = name
this.introduce = function() {
return 'My name is' + this.name
}
}
// name 프로퍼티와 introduce 메소드를 미리 초기화한 객체를 리턴합니다.
var user1 = new Person('godori')
var user2 = new Person('irodog')
user1.introduce() // My name is godori
user2.introduce() // My name is irodog
이와 같이 Person이라는 개별 객체를 new 키워드로 생성할 수 있습니다.
저는 new 키워드로 객체를 생성하지 않고 ES6의 Object.assign()
을 사용해서 팩토리 함수를 구현해 보겠습니다. 이 함수는 여러 객체의 속성을 복사해서 하나로 합칠 수 있습니다.
예를 들어, 다음 코드에서 target에 source의 속성을 복사한 returnedTarget은 target과 같은 속성들을 가집니다.
const target = { a: 1, b: 2 };
const source = { b: 4, c: 5 };
const returnedTarget = Object.assign(target, source);
console.log(target); // { a: 1, b: 4, c: 5 }
console.log(returnedTarget); // { a: 1, b: 4, c: 5 }
이를 활용해 target 대신 빈 객체 {} 를 넣고 여러 개의 source 객체를 인자로 넣으면, 빈 객체에 다른 객체들이 합쳐진 새로운 객체를 얻을 수 있습니다.
이는 연결형 상속 이라고도 하는데, 객체 속성을 복사함으로서 상속을 구현할 수 있습니다.
다음의 엔지니어를 만드는 예시를 보면 더 이해하기 쉽습니다.
const jsSkill = {
knowJS() {
return true
}
}
const javaSkill = {
knowJava() {
return true
}
}
const isWorking = {
isWorking() {
return true
}
}
const frontendEngineer = Object.assign({}, jsSkill, isWorking)
const backendEngineer = Object.assign({}, javaSkill, isWorking)
const fullStackEngineer = Object.assign({}, jsSkill, javaSkill, isWorking)
이제 이 방법을 팩토리 함수에서 사용해 봅시다.
먼저 Animal
이라는 팩토리 함수를 정의합니다. 이 함수는 내부에서 빈 객체를 만들고 name 프로퍼티와 walk라는 함수를 갖도록 초기화하여 반환합니다.
const Animal = function(name) {
const animal = {}
animal.name = name
animal.walk = function() {
console.log(this.name + ' walks');
}
return animal
}
const rabbit = Animal('rabbit');
이 Animal 팩토리로 만들어진 동물에게 특정한 기능을 주고 싶습니다. canKill 이라는 객체를 만들고 Object.assign
으로 복사하면 됩니다.
const canKill = {
kill() {
console.log('I will kill you!')
}
}
const tiger = Object.assign(Animal('tiger'), canKill)
Animal 팩토리로 만들어진 객체에게 통해 kill() 능력을 부여한 tiger 객체를 생성했습니다!
하지만 assign을 객체 생성시마다 일일이 적용해주는 건 불-편합니다. 따라서 먼저 만들었던 Animal 팩토리에서 베이스가 되는 객체를 생성하고, Object.assign
을 통해 kill 능력을 가진 동물을 생산할 수 있는 KillingAnimal 이라는 팩토리 함수를 만들었습니다.
const KillingAnimal = function(name) {
return Object.assign(Animal(name), canKill)
}
const bear = KillingAnimal('bear')
같은 방법으로 fly나 swim 등의 다양한 능력을 가진 동물 공장을 만들 수 있을 것입니다.
하는 김에 사람 공장도 만들어 보았습니다.
const HumanBorn = function(obj) {
let isWalking = false
return Object.assign({}, obj, {
walk() {
isWalking = true
},
isWalking() {
return isWalking
}
})
}
const StarkIndustry = function(obj) {
let isFlying = false
return Object.assign({}, obj, {
fly() {
isFlying = true
return this
},
isFlying() {
return isFlying
}
})
}
const tony = HumanBorn()
const ironman = StarkIndustry(tony)
const jarvis = StarkIndustry()
const vision = HumanBorn(jarvis)
흠... 빨리 가망이 없는 최종편이 보고 싶어요.
* * *
팩토리 메서드 패턴을 공부하기 위해 java로 디자인 패턴을 설명한 책을 본 다음 JavaScript로 팩토리 메소드 패턴을 이해하려다 보니 많은 혼란이 왔습니다. 나름대로 여러 자료를 참고해서 정리해 보았는데, 혹시나 잘못된 내용은 피드백 해주시면 감사하겠습니다.
가망이 없는 최종편 넘모 궁금하고~