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] 사용단순히 객체를 생성하기 위해 틀을 만드는 건 함수로도 충분할 수 있습니다. 허나, 프로토콜과 클래스를 활용하여 객체 생성 로직과 사용 로직을 분리할 수 있었습니다. 또한, 객체의 생성과 관련된 공통 로직을 묶고 하위 클래스에서 재정의한 복잡한 로직을 하위 클래스에서 재정의할 수 있게 되어 변경에 있어 유연성을 얻을 수 있었습니다.