팩토리 또는 팩토리 메서드 패턴 (Factory pattern or factory method pattern)은 생성 관점의 디자인 패턴 (Creational design patterns)의 일종으로, 인스턴스 생성을 팩토리 타입의 메서드에 위임하는 방식으로 개발자들에게 이용하는 타입에 대한 정보를 숨기고 인스턴스 생성을 용이하게 해줍니다.
현재 개발 중인 앱에는 자주 사용되는 UI 요소들을 커스터마이징한 커스텀 UI 타입이 있습니다.
import UIKit
final class MyAppLabel: UILabel {
init(textColor: CGColor = UIColor.label.cgColor, backgroundColor: UIColor = .systemBackground) {
self.textColor = textColor
self.backgroundColor = backgroundColor
}
}
final class MyAppButton: UIButton {
init(textColor: CGColor, backgroundColor: UIColor = .systemBackground) {
self.textColor = textColor
self.backgroundColor = backgroundColor
}
}
final class MyAppTextField: UITextField {
init(textColor: CGColor = UIColor.label.cgColor, backgroundColor: UIColor) {
self.textColor = textColor
self.backgroundColor = backgroundColor
}
}
각 타입에는 이니셜라이저를 통해 기본으로 설정된 값도 있고, 그렇지 않은 경우도 있습니다. 개발자들은 UI를 개발하는 동안 모든 커스텀 타입에 대해 이러한 세부 사항을 기억하고 있어야 합니다. 현재는 textColor
와 backgroundColor
에 대한 사항만을 가정하고 있지만, 실제로는 크기 또는 여백 등 더 많은 커스터마이징 요소들이 포함되어 있을 수 있습니다. 팩토리 패턴이 적용되지 않은 현재는 아래와 같이 직접 UI 요소들을 초기화하여 사용하고 있습니다.
let label = MyAppLabel()
let button = MyAppButton(textColor: UIColor.systemPink.cgColor)
let textField = MyAppTextField(backgroundColor: .systemTeal)
하지만 개발자들이 모든 커스텀 타입에 대해 이러한 세부 사항을 기억하는 것은 쉽지 않은 일일 것입니다.
그래서 각 UI 요소들을 열거형을 통해 케이스를 정의하고, 프로토콜을 통해 외부에 제공해야할 인터페이스를 정의하도록 만들어 케이스별로 특정한 UI 요소를 지칭할 수 있는 환경을 만들어줍니다.
import UIKit
enum UIComponent {
case label, button, textField
}
protocol MyAppUIComponent {
var component: UIComponent { get }
var textColor: CGColor { get set }
var backgroundColor: UIColor { get set }
}
final class MyAppLabel: UILabel, MyAppUIComponent {
var component: UIComponent { .label }
init(textColor: CGColor = UIColor.label.cgColor, backgroundColor: UIColor = .systemBackground) {
self.textColor = textColor
self.backgroundColor = backgroundColor
}
}
final class MyAppButton: UIButton, MyAppUIComponent {
var component: UIComponent { .button }
init(textColor: CGColor, backgroundColor: UIColor = .systemBackground) {
self.textColor = textColor
self.backgroundColor = backgroundColor
}
}
final class MyAppTextField: UITextField, MyAppUIComponent {
var component: UIComponent { .textField }
init(textColor: CGColor = UIColor.label.cgColor, backgroundColor: UIColor) {
self.textColor = textColor
self.backgroundColor = backgroundColor
}
}
계속해서 정의한 커스텀 UI 요소의 생성을 전문으로하는 팩토리 타입을 만듭니다.
struct ComponentFactory {
func make(_ component: UIComponent) -> MyAppUIComponent {
switch component {
case .label:
return MyAppLabel()
case .button:
return MyAppButton(textColor: UIColor.systemPink.cgColor)
case .textField:
return MyAppTextField(backgroundColor: .systemTeal)
}
}
}
이제 팩토리 타입을 이용해 원하는 UI 요소를 케이스별로 생성하여 사용할 수 있게 되었습니다. 개발자들이 UI 요소를 생성할 때 많은 것을 알 필요가 없게 되었네요.
let factory = ComponentFactory()
let label = factory.make(.label)
let button = factory.make(.button)
let textField = factory.make(.textField)
새로운 커스텀 UI 타입을 정의한다고 하더라도 인스턴스 생성에 대한 세부 사항은 팩토리 타입에서 정의해주면 됩니다. 열거형의 케이스로 정의되어 있기 때문에 새로운 케이스에 대한 생성 방식을 팩토리 타입의 make(_:)
메서드에 정의하지 않으면 컴파일 에러가 발생하여 에러 발견이 더 용이해진다는 장점도 생겼습니다.
깔끔한 정리 감사합니다! 이해가 쏙쏙 되네요!