
코틀린 Abstract Factory 패턴은 객체를 많이 만드는 기술이라기보다 서로 맞는 객체들을 한 세트로 만들기 위한 구조에 가깝습니다. 이 글에서는 제품군 일관성이 왜 중요한지, Factory Method와는 무엇이 다른지, 그리고 언제는 과한 선택이 되는지 쉽게 정리해보겠습니다.
버튼은 다크 테마인데 다이얼로그는 라이트 테마이고, 모바일용 컴포넌트와 데스크톱용 컴포넌트가 섞이면 화면은 바로 어색해집니다. Abstract Factory는 바로 이런 제품군 불일치를 줄이기 위한 패턴입니다.
문제 장면
처음에는 버튼 하나만 만들면 됩니다. 그래서 when이나 작은 팩토리 함수만으로도 충분해 보입니다.
그런데 시간이 지나면 버튼만 있는 게 아닙니다. 다이얼로그, 입력창, 카드, 메뉴처럼 같은 테마와 같은 규칙을 따라야 하는 객체들이 함께 늘어납니다.
이때 문제는 객체 수가 아니라 조합입니다. 버튼은 다크 스타일인데 다이얼로그는 라이트 스타일이면 기능은 돌아가도 경험은 바로 어색해집니다.
- 라이트 버튼 + 라이트 다이얼로그는 자연스럽다
- 다크 버튼 + 다크 다이얼로그도 자연스럽다
- 다크 버튼 + 라이트 다이얼로그처럼 섞이면 규칙이 깨진다
즉, 여기서 필요한 것은 제품 하나를 잘 만드는 기술보다 같이 움직여야 하는 객체들을 같은 계열로 계속 맞추는 구조입니다.
제품군
Abstract Factory의 핵심 단어는 제품 하나가 아니라 제품군입니다. 버튼 하나가 아니라 버튼과 다이얼로그, 필요하면 입력창까지 한 묶음으로 본다는 뜻입니다.
쉽게 말하면 팩토리가 이렇게 말해주는 구조입니다. “라이트 계열을 쓰기로 했으면 버튼도 라이트, 다이얼로그도 라이트로 줄게.”

이렇게 되면 클라이언트는 구체 클래스 이름을 몰라도 됩니다. 어떤 버튼을 고를지보다 어떤 제품군을 선택할지만 결정하면 됩니다.
구조
- Button, Dialog 같은 제품 인터페이스를 나눈다
- LightButton, DarkButton처럼 제품군별 구현체를 만든다
- 제품군 전체를 만드는 WidgetFactory 인터페이스를 둔다
- LightWidgetFactory, DarkWidgetFactory처럼 concrete factory를 나눈다
핵심은 이 한 줄입니다. 클라이언트는 제품 하나를 고르는 대신 어떤 계열을 쓸지만 고른다. 그러면 세부 객체는 그 계열 안에서 일관되게 따라옵니다.
코틀린 예제
예시는 라이트 테마와 다크 테마를 가진 UI 컴포넌트 제품군입니다. 버튼과 다이얼로그가 항상 같은 테마로 나와야 한다고 가정해보겠습니다.
interface Button {
fun render(): String
}
interface Dialog {
fun show(): String
}
class LightButton : Button {
override fun render() = "LightButton"
}
class DarkButton : Button {
override fun render() = "DarkButton"
}
class LightDialog : Dialog {
override fun show() = "LightDialog"
}
class DarkDialog : Dialog {
override fun show() = "DarkDialog"
}
interface WidgetFactory {
fun createButton(): Button
fun createDialog(): Dialog
}
class LightWidgetFactory : WidgetFactory {
override fun createButton(): Button = LightButton()
override fun createDialog(): Dialog = LightDialog()
}
class DarkWidgetFactory : WidgetFactory {
override fun createButton(): Button = DarkButton()
override fun createDialog(): Dialog = DarkDialog()
}
class ScreenRenderer(
private val factory: WidgetFactory,
) {
fun render(): List<String> {
val button = factory.createButton()
val dialog = factory.createDialog()
return listOf(button.render(), dialog.show())
}
}겉으로 보면 함수가 조금 늘어난 것처럼 보입니다. 하지만 ScreenRenderer는 이제 LightButton이나 DarkDialog 같은 구체 클래스를 몰라도 됩니다.
또 하나 중요한 점은 같은 팩토리에서 나온 제품끼리는 계열이 맞는다는 것입니다. LightWidgetFactory를 넣으면 버튼과 다이얼로그가 모두 라이트 계열로 맞춰지고, DarkWidgetFactory를 넣으면 둘 다 다크 계열로 맞춰집니다.
코드 해설
생성 책임
화면을 그리는 쪽은 이제 어떤 구체 객체를 만들지 직접 결정하지 않습니다. 생성 책임은 팩토리로 빠지고, 클라이언트는 렌더링 흐름에 더 집중할 수 있습니다.
일관성
버튼만 다크고 다이얼로그는 라이트인 이상한 조합이 줄어듭니다. 왜냐하면 같은 팩토리에서 나온 제품끼리만 함께 쓰이기 때문입니다.
교체 범위
테마를 바꾸고 싶다면 화면 코드를 뜯는 대신 팩토리 구현체만 바꾸면 됩니다. 이 점은 설정 기반 주입이나 DI와도 잘 맞습니다.
Factory Method
여기서 가장 많이 헷갈리는 질문은 이것입니다. “Factory Method도 객체를 만들고, Abstract Factory도 객체를 만드는데 뭐가 다를까?”
둘 다 생성 패턴이지만 초점이 다릅니다. 바로 앞 글인 Factory Method 패턴으로 생성 책임 나누기에서 다뤘듯, Factory Method는 보통 제품 하나의 생성 지점을 감추는 데 더 가깝습니다.
Abstract Factory 패턴
- Factory Method: 무엇을 만들까
- Abstract Factory: 어떤 계열로 묶어 만들까
즉, Factory Method가 생성 책임을 바깥으로 빼는 감각을 먼저 잡아준다면, Abstract Factory는 거기서 한 걸음 더 나아가 여러 생성 지점을 한 가족으로 묶는 패턴입니다.
좋은 경우
테마
라이트와 다크처럼 시각 규칙이 제품 전체에 걸쳐 함께 움직여야 할 때 잘 맞습니다.
플랫폼
모바일용 위젯과 데스크톱용 위젯처럼 같은 역할인데 구현 묶음이 달라질 때도 자연스럽습니다.
화이트라벨
고객사마다 버튼, 배너, 메시지 포맷, 정책 객체가 세트로 바뀌는 서비스에도 잘 어울립니다.
테스트
실제 구현 대신 fake button, fake dialog, fake notifier를 한 제품군으로 묶어 주입하고 싶을 때도 편합니다.
과한 경우
단일 제품
버튼 하나만 바꾸면 되는데 팩토리 인터페이스에 createDialog(), createMenu()까지 늘어나면 구조만 커집니다.
느슨한 조합
버튼과 다이얼로그가 항상 같은 계열로 움직이지 않는다면, 굳이 한 팩토리로 묶는 이점이 작습니다.
초기 단계
타입이 두세 개뿐이고 변경 가능성도 낮다면 companion object, top-level factory function, 혹은 작은 Factory Method 정도로 충분할 수 있습니다.
패턴 선행
실제 문제는 아직 없는데 패턴부터 만들면 인터페이스와 구현체만 늘어납니다. 제품군 일관성이 진짜 문제인지 먼저 확인하지 않으면 Abstract Factory는 쉽게 과설계가 됩니다.
코틀린 포인트
코틀린에서는 이 패턴도 조금 더 가볍게 다룰 수 있습니다. 핵심 계약은 인터페이스로 두되, 구현 클래스는 짧게 정리할 수 있고 필요하면 작은 value object나 data class로 제품 설정을 깔끔하게 분리할 수 있습니다.
또 Kotlin 공식 문서가 설명하듯 delegation은 구현 상속의 좋은 대안이 될 수 있습니다. 그래서 제품군 내부 공통 동작을 억지 상속보다 조합으로 풀면 더 읽기 좋은 경우가 많습니다.
팩토리를 object로 만들 수도 있지만, 실행 시점에 제품군이 바뀌거나 테스트에서 교체해야 한다면 너무 이른 object 고정은 오히려 유연성을 줄일 수 있습니다. 이 부분은 Singleton과 object 차이를 함께 보면 더 잘 연결됩니다.
판단 기준
- 지금 바뀌는 대상이 제품 하나인가, 제품군 전체인가?
- 같은 계열끼리 맞아야 하는 규칙이 있는가?
- 새 제품군이 추가될 가능성이 있는가?
- 클라이언트가 구체 클래스를 너무 많이 알고 있지는 않은가?
- 단순한 Factory Method나 함수 하나로도 충분하지 않은가?
이 질문에 1번과 2번이 강하게 yes라면 Abstract Factory를 검토할 이유가 있습니다. 반대로 제품 하나만 바뀌는 문제라면 Factory Method 쪽이 더 자연스러울 가능성이 큽니다.
정리
Abstract Factory는 제품 하나를 예쁘게 만드는 기술이 아니라 서로 맞는 객체들을 한 세트로 보장해야 할 때 쓰는 구조라고 이해하면 훨씬 쉽습니다.
그래서 핵심 질문도 바뀝니다. “새 객체를 하나 만드는가?”가 아니라, “같이 움직여야 하는 객체들을 같은 계열로 계속 맞출 수 있는가?”입니다.
비교용 개념 참고로는 Abstract Factory 설명, Factory Method 설명, 코틀린 쪽 관점은 Kotlin delegation 문서를 함께 보면 좋습니다. 다음 글은 Builder 패턴으로 이어가겠습니다.