추상 팩토리 패턴은 서로 관련된 객체들의 군(family)을 생성하는 인터페이스를 제공하는 패턴이다.
한 번 선택한 제품군은 일관되게 적용되어야 하고, 서로 다른 제품군의 객체가 섞이지 않도록 보장한다. GoF 생성 패턴 중 하나로, "구체적인 클래스를 지정하지 않고 관련 객체들을 생성"하는 것이 핵심이다.
| 원칙 | 설명 |
|---|---|
| 제품군 일관성 | 생성되는 객체들이 항상 같은 제품군에 속함을 보장 |
| 캡슐화 | 구체적인 클래스 생성 로직을 팩토리 내부로 숨김 |
| DIP | 클라이언트는 추상 인터페이스에만 의존 |
| OCP | 새로운 제품군 추가 시 기존 코드 수정 없이 확장 가능 |
// ❌ 직접 생성 - 서로 다른 제품군이 섞일 위험
class UIBuilder {
fun build(theme: String) {
val button = if (theme == "dark") DarkButton() else LightButton()
val checkbox = if (theme == "dark") DarkCheckbox() else LightCheckbox()
val dialog = if (theme == "dark") LightDialog() else DarkDialog() // 실수로 잘못된 테마!
// 제품군 불일치 발생
}
}- 제품군이 섞이면 UI 일관성이 깨짐
- 새로운 제품군(예: Blue 테마) 추가 시 모든 조건문 수정 필요
- 제품 종류가 늘어날수록 실수 가능성 증가
- 일관성 보장: 같은 팩토리에서 생성된 객체들은 항상 호환됨
- 제품군 전환 용이: 팩토리만 교체하면 전체 제품군이 변경됨
- 확장성: 새로운 제품군 추가가 쉬움
- 결합도 감소: 클라이언트는 구체 클래스를 알 필요 없음
┌──────────────────────────────────────────────────────────────────┐
│ │
│ ┌────────────────────┐ ┌────────────────────┐ │
│ │ <<interface>> │ │ <<interface>> │ │
│ │ AbstractFactory │ │ AbstractProductA │ │
│ │ + createProductA()│──────────────│ + operationA() │ │
│ │ + createProductB()│ └────────────────────┘ │
│ └────────────────────┘ △ │
│ △ ┌────────┴────────┐ │
│ ┌────────┴────────┐ │ │ │
│ │ │ ┌─────┴─────┐ ┌─────┴─────┐ │
│ ┌─┴───────────┐ ┌───┴─────────┐ │ProductA1 │ │ProductA2 │ │
│ │ConcreteFactory1│ConcreteFactory2│ │(Family1) │ │(Family2) │ │
│ └─────────────┘ └─────────────┘ └───────────┘ └───────────┘ │
│ │
└──────────────────────────────────────────────────────────────────┘
| 구성요소 | 역할 |
|---|---|
| AbstractFactory | 제품군 생성 메서드들을 선언하는 인터페이스 |
| ConcreteFactory | 특정 제품군의 객체들을 생성하는 구현체 |
| AbstractProduct | 생성될 제품의 공통 인터페이스 |
| ConcreteProduct | 특정 제품군에 속하는 실제 제품 구현체 |
// AbstractProduct - 제품 인터페이스들
interface Button {
fun render()
}
interface Checkbox {
fun render()
}
// ConcreteProduct - Dark 테마 제품군
class DarkButton : Button {
override fun render() = println("Dark Button")
}
class DarkCheckbox : Checkbox {
override fun render() = println("Dark Checkbox")
}
// ConcreteProduct - Light 테마 제품군
class LightButton : Button {
override fun render() = println("Light Button")
}
class LightCheckbox : Checkbox {
override fun render() = println("Light Checkbox")
}
// AbstractFactory - 팩토리 인터페이스
interface UIComponentFactory {
fun createButton(): Button
fun createCheckbox(): Checkbox
}
// ConcreteFactory - Dark 테마 팩토리
class DarkThemeFactory : UIComponentFactory {
override fun createButton(): Button = DarkButton()
override fun createCheckbox(): Checkbox = DarkCheckbox()
}
// ConcreteFactory - Light 테마 팩토리
class LightThemeFactory : UIComponentFactory {
override fun createButton(): Button = LightButton()
override fun createCheckbox(): Checkbox = LightCheckbox()
}
// 클라이언트 - 추상 인터페이스에만 의존
fun buildUI(factory: UIComponentFactory) {
val button = factory.createButton()
val checkbox = factory.createCheckbox()
button.render()
checkbox.render()
}
// 사용 - 팩토리만 교체하면 전체 제품군이 변경됨
buildUI(DarkThemeFactory()) // Dark Button, Dark Checkbox
buildUI(LightThemeFactory()) // Light Button, Light Checkbox관련된 객체 군을 생성해야 할 때만 사용한다. 단일 객체 생성에는 Factory Method가 더 적합하다.
// ❌ 과설계 - 제품이 하나뿐인데 Abstract Factory 사용
interface SingleProductFactory {
fun createProduct(): Product
}
// ✅ Factory Method로 충분
abstract class ProductFactory {
abstract fun create(): Product
}새로운 제품 종류(예: Dialog)를 추가하면 모든 팩토리 인터페이스와 구현체를 수정해야 한다.
// 새로운 제품 추가 시
interface UIComponentFactory {
fun createButton(): Button
fun createCheckbox(): Checkbox
fun createDialog(): Dialog // 모든 ConcreteFactory에 구현 필요
}Abstract Factory 내부에서 각 제품을 생성하는 메서드는 사실상 Factory Method다.
| 상황 | 예시 |
|---|---|
| 관련 객체들의 일관성 필요 | UI 테마, OS별 위젯 |
| 제품군 전환이 필요 | 개발/운영 환경별 설정 |
| 플랫폼 독립적 설계 | 크로스 플랫폼 앱 |
| 프레임워크 확장 포인트 | 플러그인 시스템 |
// AbstractProduct - 저장소 인터페이스
interface Storage {
fun save(data: String)
}
interface Cache {
fun get(key: String): String?
}
// AbstractFactory - 저장소 팩토리 인터페이스
interface StorageFactory {
fun createStorage(): Storage
fun createCache(): Cache
}
// ConcreteFactory - AWS 제품군
@Component
@Profile("aws")
class AWSStorageFactory : StorageFactory {
override fun createStorage(): Storage = S3Storage()
override fun createCache(): Cache = ElastiCache()
}
// ConcreteFactory - GCP 제품군
@Component
@Profile("gcp")
class GCPStorageFactory : StorageFactory {
override fun createStorage(): Storage = GcsStorage()
override fun createCache(): Cache = Memorystore()
}
// 클라이언트 - 추상 인터페이스에만 의존
@Service
class DataService(
private val storageFactory: StorageFactory
) {
fun process(data: String) {
val storage = storageFactory.createStorage()
val cache = storageFactory.createCache()
storage.save(data)
// 항상 같은 클라우드 제품군 사용 보장
}
}장점: Profile만 변경하면 전체 클라우드 인프라 제품군이 일괄 교체됨
// ❌ Before: 조건문 기반 - 제품군 섞임 위험
class DatabaseService(private val env: String) {
fun getConnection(): Connection {
return if (env == "prod") ProdConnection() else DevConnection()
}
fun getRepository(): Repository {
return if (env == "prod") ProdRepository() else DevRepository()
}
fun getCache(): Cache {
// 실수로 prod 환경에 dev 캐시 사용 가능
return if (env == "dev") ProdCache() else DevCache() // 버그!
}
}
// ✅ After: Abstract Factory - 제품군 일관성 보장
interface DatabaseFactory {
fun createConnection(): Connection
fun createRepository(): Repository
fun createCache(): Cache
}
class ProdDatabaseFactory : DatabaseFactory {
override fun createConnection() = ProdConnection()
override fun createRepository() = ProdRepository()
override fun createCache() = ProdCache() // 항상 Prod 제품군
}
class DevDatabaseFactory : DatabaseFactory {
override fun createConnection() = DevConnection()
override fun createRepository() = DevRepository()
override fun createCache() = DevCache() // 항상 Dev 제품군
}
class DatabaseService(private val factory: DatabaseFactory) {
fun setup() {
val conn = factory.createConnection()
val repo = factory.createRepository()
val cache = factory.createCache()
// 제품군 섞임 불가능
}
}| 패턴 | 차이점 |
|---|---|
| Factory Method | 단일 객체 생성을 서브클래스에 위임 |
| Abstract Factory | 관련 객체 군 생성, 여러 Factory Method 포함 |
| Builder | 복잡한 객체를 단계별로 생성, 생성 과정에 초점 |
| Prototype | 기존 객체를 복사하여 새 객체 생성 |
| 비교 항목 | Factory Method | Abstract Factory |
|---|---|---|
| 생성 대상 | 단일 제품 | 제품군(여러 관련 제품) |
| 확장 방식 | 서브클래스 추가 | 팩토리 구현체 추가 |
| 복잡도 | 상대적으로 단순 | 구조가 복잡 |
| 사용 시점 | 제품 종류 확장 | 제품군 전환 필요 |
- Head First Design Patterns - 피자 재료 팩토리 예제