들어가기에 앞서...
이번 패턴의 설명 역시 지난 패턴의 예제를 이용했다.
팩토리 메소드 패턴
팩토리 메소드 패턴은 함수에게 다른 객체(product)의 생성을 맡기는 디자인 패턴이다. creator 함수는 product 객체의 일종의 생성자 역할을 한다고 볼 수 있다.
장점
- 객체의 생성과 구현을 분리해낸다. 이전의 생성 패턴들과 같다.
- 코드의 가독성이 높아진다. 빌더 패턴과 비슷한 이유이다. 빌더 패턴이 거대한 객체의 생성을 목적으로 한다면 팩토리 메소드는 작은 객체의 생성을 목적으로 한다.
- 다른 패턴과 쉽게 연동된다. 기존의 클래스에 해당 메소드만 추가하면 되는 만큼 다른 패턴에 적용하기 쉽다. 추상 팩토리 패턴은 여러 팩토리 메소드를 묶어 객체화한 패턴이다.
단점
- 코드가 난잡해질 수 있다. 팩토리 메소드 패턴은 적용하기가 매우 쉽기 때문에 남용할 수 있다. 즉, 이미 적당한 생성자가 있음에도 불구하고 팩토리 메소드 패턴을 사용하면 한 객체에 두가지 생성자가 만들어질 우려가 있다.
구현
사실 우리는 이미 팩토리 메소드 패턴을 구현했다. 다음은 기존 예제의 함수이다.
func NewRoom(idx int) *room {
r := new(room)
r.idx = idx
for i := range r.sides {
r.sides[i] = NewWall()
}
return r
}
// ...
func NewRoomWithBomb(idx int) *roomWithBomb {
r := new(roomWithBomb)
r.Room = NewRoom(idx)
return r
}
새로운 room을 반환하는 함수이다. 이 때 room에 대한 기본적인 처리는 함수 내에서 완료되어 반환된다. 해당 함수를 다듬으면 다음과 같은 모습이 된다.
func NewRoom(idx int) Room {
r := new(room)
r.idx = idx
for i := range r.sides {
r.sides[i] = NewWall()
}
return r
}
//...
func NewRoomWithBomb(idx int) Room {
r := new(roomWithBomb)
r.Room = NewRoom(idx)
return r
}
위의 함수가 바로 팩토리 메소드이다. 위와 달라진 점은 인터페이스로 선언되었다는 것이다. 이는 해당 객체의 내부 변수 및 내부 메소드로의 접근을 제한하고 인터페이스에 정의된 함수만 이용할 수 있게한다. 즉, 해당 객체는 은닉성을 얻게 된다.
팩토리 메소드는 매개변수를 이용해서 생성할 객체를 결정할 수도 있다.
package main
type mazeType int
const (
simple mazeType = iota
bomb
)
func NewRoomOf(t mazeType, idx int) Room {
switch t {
case simple:
return NewRoom(idx)
case bomb:
return NewRoomWithBomb(idx)
default:
warn()
return nil
}
}
func NewDoorOf(t mazeType, r1, r2 Room) Door {
switch t {
case simple:
return NewDoor(r1, r2)
case bomb:
return NewBombedDoor(r1, r2)
default:
warn()
return nil
}
}
func warn() {
panic("Invalid argument")
}
해당 함수는 매개변수를 받아 그에 맞춰 생성할 객체를 결정한다. 오류의 가능성을 줄이기 위해 mazeType 자료형을 선언하고 해당 자료형의 상수를 열거형으로 정의했다.
통상적인 팩토리 메소드는 이미 많이 사용했으므로 매개변수를 이용한 팩토리 메소드의 호출만 보고 마무리짓겠다.
//...
func CreateMaze2(t mazeType) Maze {
m := NewMaze()
r1 := NewRoomOf(t, 1)
r2 := NewRoomOf(t, 2)
d := NewDoorOf(t, r1, r2)
m.AddRoom(r1)
m.AddRoom(r2)
r1.SetSide(East, d)
r2.SetSide(West, d)
return m
}
func main() {
simpleM := CreateMaze()
play(simpleM)
m := CreateMaze2(simple)
play(m)
m = CreateMaze2(bomb)
play(m)
}
Entered to room 1
door is Open. Successfully entered.
Entered to room 1
Bomb!
Door was bombed. Successfully entered.
마치며...
팩토리 메소드 패턴과 추상 팩토리 패턴은 묶어서 팩토리 패턴이라고 불리기도 한다. 그만큼 팩토리 패턴과 팩토리 메소드 패턴은 같이 쓰이는 경우가 많다.
팩토리 메소드 패턴은 범용적으로 쓰이지만 특별한 형식을 요하지 않는 만큼 패턴이라 하기 애매하기도 하다. 그래서 해당 패턴은 혼자 쓰이기 보다는 다른 패턴에 곁들어서 쓰인다. 다른 패턴들이 하나의 완성된 음식이라면 팩토리 메소드는 조미료인 것이다.
참고 자료
- GoF(에릭 감마, 리처드 헬름, 랄프 존슨, 존 블리시디스), GoF의 디자인 패턴, 프로텍 미디어, 2015
- Dmitri Nesteruk, Design Patterns in Go, Udemy, 2020.8