Factory Method Pattern in Swift

design-pattern
Written on Aug 13, 2023


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() 메서드를 제공한다고 가정해봅시다.

Language:swift
protocol Product {
    var name: String { get }

    func use()
}

그리고 Creator 역할을 수행하는 Factory 프로토콜을 정의합니다. 제품 Product를 생성하는 역할을 수행하는 Factorycreate() factory method를 가집니다. create() 메서드는 Product를 생성하고 register() 메서드를 호출하며 하위 클래스에서 정의한 구체적인 동작을 수행하는 등 복잡한 로직을 수행한다고 가정합니다.

Language:swift
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 클래스를 정의합니다.

Language:swift
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 클래스를 정의합니다.

Language:swift
final class IDCardFactory: Factory {
    func createProduct(owner: String) -> Product {
        return IDCard(owner: owner)
    }

    func register(product: Product) {
        print("\(product.name) 등록")
    }
}

이를 토대로 IDCardFactory를 통해 제품을 생산하는 예제를 작성해봅시다.

Language:swift
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] 사용

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