[Design Pattern] 내가 보려고 정리하는 Swift 디자인 패턴 (6) - 팩토리 패턴(Factory Pattern)

2024. 7. 1. 13:55Architecture, Design Pattern

1️⃣ 팩토리 패턴 왜 쓰는 건데?

팩토리 패턴 (Factory Pattern)객체의 생성을 "팩토리"라는 별도의 클래스로 분리하여, 추상화된 부분에 의존하도록 만드는 패턴이다.

가장 근본적으로는 팩토리 패턴으로 객체 생성 과정의 책임을 분리할 수 있으며,
더 나아가 복잡한 객체를 생성하는 과정을 숨길 수 있고, 객체(= UI Component) 생성 과정에서 변경사항이 생겼을 때 수정이나 확장이 용이하다는 장점을 가지게 된다.

팩토리 패턴은 크게 다시, 팩토리 메서드 패턴 (Factory Method Pattern)과 추상 팩토리 패턴 (Abstract Factory Pattern)으로 나눌 수 있다. 자세한 내용은 아래에서 순서대로 코드와 예시와 함께 살펴보도록 하겠다.

  • Factory Method : 단일 객체 생성만을 담당하는 추상화된 프로토콜 생성, 객체 생성은 프로토콜의 구체화 클래스에서 담당
  • Abstract Factory : 관련된 객체를 묶어서 생성할 수 있는 추상화된 프로토콜 생성, 객체 생성은 마찬가지 구체화 클래스에서 담당

 

2️⃣ 팩토리 패턴 (Factory Pattern)에서 사용하는 용어 살펴보기

본격적인 패턴을 Swift 코드로 살펴보기 전에, Factory Pattern에서 사용되는 용어와 가정 상황을 먼저 설명해 보겠다.

같은 프로젝트를 진행 중인 iOS와 안드로이드 개발자는 하루하루 커스텀 뷰를 찍어내야 하는 "뷰 공장"이 가동 중에 있다고 생각해 보자.
커스텀 뷰는 iOS 버전과 안드로이드 버전 각각 다양하게 필요하다고 한다. (버튼, 라벨, 텍스트필드 등등..)

  • Abstract Product : 생성하고자 하는 객체를 추상화한 Protocol -> "뷰 공장"에서 "뷰"라고 가정!
  • Concrete Product : Abstract Product를 구체적으로 구현한 Class -> 다양한 뷰 (버튼, 라벨, 텍스트필드 등..)라고 가정!
// Abstract Product
protocol View {}

// Concrete Product
class IOSCustomButton: View {}
class AndroidCustomButton: View {}
...

 

3️⃣ 팩토리 메서드 패턴 (Factory Method Pattern) 이해하기 

💡 팩토리 메서드 패턴 (Factory Method Pattern)은 단일 객체 생성만을 담당하는 추상화된 프로토콜 생성한 후, 객체 생성은 프로토콜의 구체화 클래스에서 담당하게 하는 패턴이다.

팩토리 메서드 패턴에서는 객체를 생성할 수 있는 별도의 추상화된 프로토콜(= Abstract Creator)을 만들어둔다.
이 프로토콜 안에는 객체를 실제로 (공장처럼) 만들어내는 메서드 (= Factory Method)가 존재한다.

우리가 만들어야 할 커스텀 뷰의 종류는 매우 다양하지만 (버튼도 있고, 라벨도 있고, 텍스트 뷰도 있고...),
이 프로토콜을 구체화한 클래스 (= Concrete Creator)를 만들었을 때, 팩토리 메서드의 구현부에는 단 한 종류의 객체만 생성할 수 있게 되는 것이 특징이다.

쉽게 말해, 버튼 공장은 버튼만 만들 수 있고, 라벨 공장은 라벨만 만들 수 있게 되는 셈이다!
*참고로 Swift API Design Guideline에 따르면 팩토리 메서드의 이름은 make~로 시작하는 것을 권장하고 있다.

// Abstract Creator
protocol ViewFactory {
    // FactoryMethod
    func makeCustomView(with: Platform) -> View
}

// Concrete Creator
class CustomButtonFactory: ViewFactory {
    func makeCustomView(with: Platform) -> View {
        switch (with) {
        case .iOS: 
            return IOSCustomButton()
        case .android: 
            return AndroidCustomButton()
        }
    }
}
class CustomLabelFactory: ViewFactory {
    func makeCustomView(with: Platform) -> View {
        switch (with) {
        case .iOS:
            return IOSCustomLabel()
        case .android:
            return AndroidCustomLabel()
        }
    }
}

그럼 실제 뷰의 생성이 필요한 경우에는
아래와 같이 버튼이라면 버튼 공장에서, 라벨이라면 라벨 공장에서 팩토리 메서드(Factory Method)를 사용해 만들 수 있게 되는 것이다.

처음 이 글에서 만들었던 것처럼 생성 과정을 숨길 수 (= 객체 생성 부분을 별도의 추상화 프로토콜로 분리) 있으며,
만약 객체 생성 과정에 대해 변경사항이 생겼을 때 해당 팩토리 메서드만 수정하면 되는 셈이다!

let customButtonFactory = CustomButtonFactory()
let iOSButton = customButtonFactory.makeCustomView(with: .iOS)
let androidButton = customButtonFactory.makeCustomView(with: .android)

팩토리 메서드 패턴을 아래처럼 UML 클래스 다이어그램으로 직관적이게 표현할 수도 있다.

 

4️⃣ 추상 팩토리 패턴 (Abstract Factory Pattern) 이해하기 

💡 추상 팩토리 패턴 (Abstract Factory Pattern)은 관련된 객체를 묶어서 생성할 수 있는 추상화된 프로토콜 생성한 후, 객체 생성은 마찬가지 구체화 클래스에서 담당하게 하는 패턴이다.

팩토리 메서드 패턴에서 생기는 문제점은 "한 공장 당 한 객체만 만들지 못한다는 점"이다.

쉽게 말해 버튼을 만들려면 버튼 공장이 필요하고, 라벨을 만드려면 라벨 공장을 매번 만들어줘야 하는 번거로움 이슈가 있다는 것이다.
더불어, iOS와 안드로이드 버전에 상관없이 그저 객체 종류에 맞춰 뷰를 찍어내다 보니 같은 플랫폼을 보장하는 뷰를 만들지 못한다는 문제가 생길 수 있다.

이를 해결하기 위해 등장한 것이 추상 팩토리 패턴 (Abstract Factory Pattern)이다.
핵심은 아주 단순! "한 팩토리에 같은 유형에 객체를 생성할 수 있도록 메서드들을 묶어서 만드는 것"이라고 보면 되겠다.

// Abstract Factory
protocol ViewAbstractFactory {
    func makeCustomButton() -> View
    func makeCustomLabel() -> View
    func makeCustomTextField() -> View
}

그럼 실제 이를 채택한 클래스에서는 각 메서드별로 같은 유형의 객체를 생성할 수 있도록 선언해주기만 하면 된다!

class IOSCustomViewFactory: ViewAbstractFactory {
    func makeCustomButton() -> View {
        return IOSCustomButton()
    }
    
    func makeCustomLabel() -> View {
        return IOSCustomLabel()
    }
    
    func makeCustomTextField() -> View {
        return IOSCustomTextField()
    }
}

클래스 다이어그램은 아래와 같이 표현할 수 있지!

여기까지가 기본적인 Swift 팩토리 패턴의 구현 내용이 되겠고,
다음에는 실제 팩토리 패턴을 코드에서 어떻게 적용할 수 있는지를 글로 다뤄보도록 하겠다. 오늘은 여기까지!