팩토리 패턴이란 무엇인가?
팩토리 패턴은 객체 생성을 캡슐화하는 생성 패턴 중 하나입니다.
직접 init()을 호출해서 객체를 만드는 대신, 팩토리라는 중간 관리자를 두고 이를 통해 객체를 생성하는 방식이에요.
저도 처음 iOS 개발을 시작했을 때는 그냥 필요한 곳에서 바로 객체를 만들면 되는 거 아닌가? 라고 생각했었는데,
프로젝트가 커질수록 이런 방식의 문제점을 절실히 느끼게 되더라고요 😅
왜 팩토리 패턴을 사용해야 할까?
1. 런타임에 객체 타입 결정
사용자 입력이나 네트워크 응답에 따라 다른 타입의 객체를 만들어야 할 때가 있어요.
예를 들어, SNS 앱에서 피드 타입(텍스트, 이미지, 비디오)에 따라 다른 셀을 생성해야 하는 경우입니다.
2. 복잡한 초기화 로직 숨기기
객체를 만들 때 복잡한 설정이나 의존성 주입이 필요한 경우, 클라이언트 코드가 이런 복잡성을 알 필요가 없죠.
3. 확장성 보장
새로운 타입이 추가될 때마다 기존 코드를 수정하는 것이 아니라, 팩토리만 확장하면 되니까 개방-폐쇄 원칙(OCP)을 지킬 수 있어요.
이런 장점들 때문에 팩토리 메서드 패턴은 코드에 높은 수준의 유연성을 제공해야 할 때 매우 유용합니다.
팩토리 메서드 패턴은 단일 객체 생성에 초점을 맞춘 패턴입니다. 하나의 제품을 만드는데, 그 제품의 구체적인 타입은 서브클래스가 결정하도록 하는 방식이에요.
실제 상황: 배송 관리 앱 📦📦📦 + 🚢?
iOS 앱에서 배송 관리 시스템을 만든다고 가정해봅시다. 처음에는 트럭 배송만 지원했는데,
갑자기 선박 배송도 추가해달라는 요청이 들어왔어요.
기존 방식이라면:
// 기존 코드 - 문제가 있는 방식
func createDelivery(type: String) -> Transport {
if type == "truck" {
return Truck()
} else if type == "ship" {
return Ship()
} else {
fatalError("Unknown transport type")
}
}
이렇게 하면 새로운 운송 수단이 추가될 때마다 이 함수를 계속 수정해야 하겠죠?
그리고 만약 비행기, 드론 등이 추가된다면? 조건문 지옥이 펼쳐질 거예요 😱
팩토리 메서드 패턴으로 해결하기
// Product 프로토콜 - 모든 운송 수단의 공통 인터페이스
protocol Transport {
func deliver() -> String
func getCapacity() -> Int
func getSpeed() -> Int
}
// Concrete Products - 구체적인 운송 수단들
class Truck: Transport {
func deliver() -> String {
return "🚛 육로로 배송 중입니다"
}
func getCapacity() -> Int { return 1000 }
func getSpeed() -> Int { return 80 }
}
class Ship: Transport {
func deliver() -> String {
return "🚢 해상으로 배송 중입니다"
}
func getCapacity() -> Int { return 5000 }
func getSpeed() -> Int { return 30 }
}
class Airplane: Transport {
func deliver() -> String {
return "✈️ 항공으로 배송 중입니다"
}
func getCapacity() -> Int { return 800 }
func getSpeed() -> Int { return 900 }
}
// Creator 프로토콜 - 팩토리 메서드를 정의
protocol Logistics {
func createTransport() -> Transport // 팩토리 메서드
func planDelivery() -> String
}
// 비즈니스 로직을 포함한 기본 구현
extension Logistics {
func planDelivery() -> String {
let transport = createTransport()
let deliveryInfo = transport.deliver()
let capacity = transport.getCapacity()
let speed = transport.getSpeed()
return """
\(deliveryInfo)
적재량: \(capacity)kg, 속도: \(speed)km/h
"""
}
}
// Concrete Creators - 구체적인 물류 팩토리들
class RoadLogistics: Logistics {
func createTransport() -> Transport {
return Truck()
}
}
class SeaLogistics: Logistics {
func createTransport() -> Transport {
return Ship()
}
}
class AirLogistics: Logistics {
func createTransport() -> Transport {
return Airplane()
}
}
클라이언트 코드가 얼마나 깔끔해지는지 봅시다
class DeliveryManager {
func processDelivery(logistics: Logistics) {
print(logistics.planDelivery())
}
}
// 사용 예시
let manager = DeliveryManager()
manager.processDelivery(logistics: RoadLogistics())
// 출력: 🚛 육로로 배송 중입니다
// 적재량: 1000kg, 속도: 80km/h
manager.processDelivery(logistics: SeaLogistics())
// 출력: 🚢 해상으로 배송 중입니다
// 적재량: 5000kg, 속도: 30km/h
추상 팩토리 패턴
관련된 객체들의 집합을 생성하는 데 초점을 맞춘 패턴입니다.
- 어떻게 호환되는 객체 군을 생성할 것인가에 초점
실제 상황: 가구 쇼핑 앱 🛋️🛋️
가구 앱을 만든다고 해봅시다. 고객들은 모던 스타일로 맞춘 세트나 구찌 스타일로 맞춘 세트를 원해요.
각 스타일별로 의자, 소파, 테이블이 모두 일관된 디자인을 가져야 하죠.
// Abstract Products - 가구의 공통 인터페이스
protocol Chair {
func sitOn() -> String
func getDesignDetails() -> String
}
protocol Sofa {
func lieOn() -> String
func getDesignDetails() -> String
}
protocol Table {
func placeItems() -> String
func getDesignDetails() -> String
}
// Modern Style Concrete Products
class ModernChair: Chair {
func sitOn() -> String {
return "🪑 세련된 모던 의자에 앉았습니다"
}
func getDesignDetails() -> String {
return "미니멀 디자인, 크롬 프레임, 가죽 시트"
}
}
class ModernSofa: Sofa {
func lieOn() -> String {
return "🛋️ 편안한 모던 소파에 누웠습니다"
}
func getDesignDetails() -> String {
return "L자형 디자인, 패브릭 소재, 낮은 백레스트"
}
}
class ModernTable: Table {
func placeItems() -> String {
return "📱 유리 테이블에 물건을 올렸습니다"
}
func getDesignDetails() -> String {
return "강화유리 상판, 스테인리스 다리"
}
}
// Victorian Style Concrete Products
class VictorianChair: Chair {
func sitOn() -> String {
return "👑 고풍스러운 빅토리안 의자에 앉았습니다"
}
func getDesignDetails() -> String {
return "조각 장식, 마호가니 원목, 벨벳 시트"
}
}
class VictorianSofa: Sofa {
func lieOn() -> String {
return "🏰 럭셔리한 빅토리안 소파에 누웠습니다"
}
func getDesignDetails() -> String {
return "카브리올 다리, 실크 소재, 높은 백레스트"
}
}
class VictorianTable: Table {
func placeItems() -> String {
return "📜 고급스러운 원목 테이블에 물건을 올렸습니다"
}
func getDesignDetails() -> String {
return "마호가니 원목, 조각 장식, 서랍 3개"
}
}
// Abstract Factory - 가구 집합을 생성하는 인터페이스
protocol FurnitureFactory {
func createChair() -> Chair
func createSofa() -> Sofa
func createTable() -> Table
}
// Concrete Factories - 스타일별 팩토리들
class ModernFurnitureFactory: FurnitureFactory {
func createChair() -> Chair {
return ModernChair()
}
func createSofa() -> Sofa {
return ModernSofa()
}
func createTable() -> Table {
return ModernTable()
}
}
class VictorianFurnitureFactory: FurnitureFactory {
func createChair() -> Chair {
return VictorianChair()
}
func createSofa() -> Sofa {
return VictorianSofa()
}
func createTable() -> Table {
return VictorianTable()
}
}
클라이언트에서 사용하기
class FurnitureShop {
private let factory: FurnitureFactory
init(factory: FurnitureFactory) {
self.factory = factory
}
func setupShowroom() -> [String] {
let chair = factory.createChair()
let sofa = factory.createSofa()
let table = factory.createTable()
return [
chair.sitOn(),
sofa.lieOn(),
table.placeItems(),
"=== 디자인 세부사항 ===",
chair.getDesignDetails(),
sofa.getDesignDetails(),
table.getDesignDetails()
]
}
}
// 사용 예시
let modernShop = FurnitureShop(factory: ModernFurnitureFactory())
let victorianShop = FurnitureShop(factory: VictorianFurnitureFactory())
print("모던 쇼룸:")
modernShop.setupShowroom().forEach { print($0) }
print("\n빅토리안 쇼룸:")
victorianShop.setupShowroom().forEach { print($0) }
이렇게 하면 새로운 스타일(예: 스칸디나비아, 산업풍)이 추가되어도 기존 클라이언트 코드는 전혀 건드릴 필요가 없어요!
팩토리 메서드 vs 추상 팩토리
두 패턴의 핵심 차이점을 정리해보면:
특성 | 팩토리메서드 | 추상팩토리 |
초점 | 단일 객체 생성 | 관련된 객체 집합 생성 |
구현 방식 | 상속 (Inheritance) | 구성 (Composition) |
확장성 | 서브클래스 추가 | 새 팩토리 클래스 추가 |
복잡도 | 상대적으로 단순 | 더 복잡하지만 강력함 |
언제 어떤 것을 선택할까?
팩토리 메서드를 선택하는 경우:
- 하나의 제품군 내에서 다양한 변형을 만들 때
- 상속 구조가 자연스러운 경우
- 상대적으로 단순한 객체 생성
추상 팩토리를 선택하는 경우:
- 여러 관련 객체를 일관성 있게 생성해야 할 때
- 런타임에 전체 제품군을 교체해야 할 때
- 플랫폼별 또는 테마별 구현이 필요한 경우
저는 보통 단일 객체 생성이면 팩토리 메서드를, UI 테마나 플랫폼별 구현처럼 여러 관련 객체를 함께 다뤄야 하면 추상 팩토리를 선택해요.
참고자료
https://refactoring.guru/design-patterns/factory-method
Factory Method
/ Design Patterns / Creational Patterns Factory Method Also known as: Virtual Constructor Intent Factory Method is a creational design pattern that provides an interface for creating objects in a superclass, but allows subclasses to alter the type of objec
refactoring.guru
https://refactoring.guru/design-patterns/factory-method
'디자인패턴' 카테고리의 다른 글
전략패턴 (0) | 2025.09.29 |
---|---|
커맨드 패턴 (0) | 2025.05.20 |
데코레이터패턴 (1) | 2025.05.19 |
파사드 패턴(Facade Pattern) (0) | 2025.03.25 |
Adaptor Pattern (구조적 디자인패턴) (0) | 2025.03.24 |