Factory Method Pattern은 앞서 알아본 Template Method Pattern과 유사합니다. 특정 인스턴스를 생성하는 메서드를 템플릿 메서드로 정의한 것 뿐입니다. 그렇기에 Template Method Pattern의 특징 그대로 인스턴스를 생성하는 공통된 로직을 템플릿화하여 재사용할 수 있습니다. 동일한 유형의 다양한 객체를 생성하는데 여러 단계의 처리나 조건부 로직이 필요할 때 패턴을 적용해볼 수 있습니다.
Factory Method Pattern
은 GoF의 분류 체계에서 생성(Creational) 패턴에 속합니다.
Factory Method Pattern을 구현하기 위해 필요한 역할은 다음과 같습니다.
- Product(제품): Factory Method로 생성되는 객체의 인터페이스를 결정합니다.
- Creator(생산자): Product 역할을 생성하는 추상 클래스입니다.
- ConcreteProduct(구체적인 제품): Product 역할의 인스턴스를 실제로 생성합니다.
- ConcreteCreator(구체적인 생산자): Product 역할의 인스턴스를 생성하는 클래스입니다.
Java 언어로 배우는 디자인 패턴 입문의 예제를 Swift 코드로 작성해봅시다.
먼저 Product
프로토콜을 정의합니다. 모든 Product
는 이름(name
)을 가지고 가장 무난한 use()
메서드를 제공한다고 가정해봅시다.
protocol Product {
var name: String { get }
func use()
}
그리고 Creator 역할을 수행하는 Factory
프로토콜을 정의합니다. 제품 Product
를 생성하는 역할을 수행하는 Factory
는 create()
factory method를 가집니다. create()
메서드는 Product
를 생성하고 register()
메서드를 호출하며 하위 클래스에서 정의한 구체적인 동작을 수행하는 등 복잡한 로직을 수행한다고 가정합니다.
protocol Factory {
func createProduct(owner: String) -> Product
func register(product: Product)
}
extension Factory {
func create(owner: String) -> Product {
let product: Product = createProduct(owner: owner)
register(product: product)
return product
}
}
생산하고자 하는 ConcreteProduct인 IDCard
클래스를 정의합니다.
final class IDCard: Product {
var name: String { "[ID Card: \(owner)]" }
private(set) var owner: String
init(owner: String) {
print("카드를 생성합니다. (소유자: \(owner))")
self.owner = owner
}
func use() {
print("\(self.name) 사용")
}
}
IDCard
클래스는 Factory
에 전혀 의존하고 있지 않습니다. 그렇기에 하나의 Product를 생산하는 Factory는 여러 종류가 될 수 있다고 예상할 수 있습니다.
IDCard
를 생산하기 위해 구체적인 내용을 담고 있는 IDCardFactory
클래스를 정의합니다.
final class IDCardFactory: Factory {
func createProduct(owner: String) -> Product {
return IDCard(owner: owner)
}
func register(product: Product) {
print("\(product.name) 등록")
}
}
이를 토대로 IDCardFactory
를 통해 제품을 생산하는 예제를 작성해봅시다.
let factory: Factory = IDCardFactory()
let products = [
factory.create(owner: "Kim"),
factory.create(owner: "Park"),
factory.create(owner: "Son"),
]
for product in products {
product.use()
}
// output:
// 카드를 생성합니다. (소유자: Kim)
// [ID Card: Kim] 등록
// 카드를 생성합니다. (소유자: Park)
// [ID Card: Park] 등록
// 카드를 생성합니다. (소유자: Son)
// [ID Card: Son] 등록
// [ID Card: Kim] 사용
// [ID Card: Park] 사용
// [ID Card: Son] 사용
단순히 객체를 생성하기 위해 틀을 만드는 건 함수로도 충분할 수 있습니다. 허나, 프로토콜과 클래스를 활용하여 객체 생성 로직과 사용 로직을 분리할 수 있었습니다. 또한, 객체의 생성과 관련된 공통 로직을 묶고 하위 클래스에서 재정의한 복잡한 로직을 하위 클래스에서 재정의할 수 있게 되어 변경에 있어 유연성을 얻을 수 있었습니다.