객체지향 설계를 배울 때는 보통 “행동”에 먼저 눈이 갑니다. 그런데 실제 코드베이스에서는 생각보다 빨리 “생성” 자체가 별도 문제로 커집니다.
이런 질문이 생기기 시작하죠.
- 여기서는 어떤 구현체를 만들어야 하는가
- 환경이나 설정에 따라 선택이 달라지는가
- 객체가 사용 가능해지기 전에 어떤 초기화가 필요한가
- 호출자가 concrete class를 너무 많이 알지 않게 하려면 어떻게 해야 하는가
이 질문이 여러 파일로 퍼지기 시작하면, 객체 생성은 더 이상 사소한 디테일이 아닙니다. 설계 문제가 됩니다.
이때 팩토리 패턴이 도움이 됩니다.
이 글에서는 아래 내용을 다룹니다.
- 팩토리 패턴이 실제로 무엇을 바꾸는지
- 왜 객체 생성이 결합도를 높이기 쉬운지
- 어떤 상황에서 팩토리가 특히 잘 맞는지
- strategy, DIP, DI와는 어떤 관계인지
- 언제는 팩토리가 도움이 되고 언제는 그냥 의식만 늘어나는지
짧게 요약하면 이렇습니다. 팩토리 패턴은 생성 책임과 사용 책임을 분리해서, 객체를 쓰는 코드가 구체적인 생성 세부사항에 덜 묶이게 만드는 구조입니다.
왜 객체 생성이 설계 문제가 되는가
처음에는 직접 생성이 전혀 문제 없어 보입니다.
const notifier = new SlackNotifier();
하지만 생성이 아래 같은 규칙에 영향을 받기 시작하면 이야기가 달라집니다.
- 환경
- 사용자 플랜
- 기능 플래그
- 자격 증명
- fallback 규칙
- 여러 단계 초기화
이때 호출자는 더 이상 단순히 객체를 “사용”만 하는 게 아닙니다. 동시에:
- 어떤 concrete type을 고를지
- 어떤 설정으로 만들지
- 어떤 조건이 선택에 영향을 주는지
까지 결정하게 됩니다.
이 지식이 코드베이스 전체로 퍼지면 결합도도 함께 퍼집니다.
팩토리가 중요한 진짜 이유는 여기 있습니다. 문제는 new 자체가 아니라, 생성에 관한 지식이 통제 없이 흩어지는 것입니다.
팩토리 패턴이 실제로 바꾸는 것
기본 움직임은 단순합니다.
- 객체를 여러 곳에서 직접 생성하는 대신
- 생성 전용 위치에 그 책임을 위임합니다
그러면 호출자는 “무엇이 필요한지”를 말하고, 팩토리는 “어떻게 만들지”를 결정합니다.
이 구조는 아래를 한곳에 모으는 데 도움이 됩니다.
- 구현체 선택
- 초기화 규칙
- 생성 입력 검증
- fallback 선택
- 환경별 wiring
즉 장점은 단순히 “생성자 호출이 줄었다”가 아닙니다. 생성 로직이 시스템 여기저기로 새지 않는다는 점이 핵심입니다.
가장 이해하기 쉬운 감각
팩토리 패턴은 두 책임을 분리해서 보면 가장 쉽게 이해됩니다.
- 사용 책임: “알림을 보낼 수 있는 무언가가 필요하다”
- 생성 책임: “설정과 환경에 따라 올바른 notifier 구현체를 만든다”
한 모듈이 이 두 일을 동시에 맡으면 필요 이상으로 결합되기 쉽습니다.
생성이 팩토리로 빠지면, 호출자는 추상화에만 의존하고 구체적인 선택은 팩토리가 대신할 수 있습니다.
이 때문에 팩토리는 인터페이스와 의존성 역전과도 자연스럽게 붙습니다.
실전 TypeScript 예시
런타임 설정에 따라 다른 결제 구현체를 써야 하는 시스템을 생각해 봅시다.
interface PaymentStrategy {
pay(amount: number): void;
}
class CardPayment implements PaymentStrategy {
pay(amount: number): void {
console.log(`Card payment: ${amount}`);
}
}
class PointsPayment implements PaymentStrategy {
pay(amount: number): void {
console.log(`Points payment: ${amount}`);
}
}
class PaymentFactory {
static create(method: 'card' | 'points'): PaymentStrategy {
if (method === 'card') {
return new CardPayment();
}
return new PointsPayment();
}
}
const payment = PaymentFactory.create('card');
payment.pay(100);
예시는 작지만 핵심은 잘 드러납니다.
- 호출자는 payment strategy가 필요하다고만 말하고
- 어떤 concrete class를 만들지는 factory가 알고
- 생성 규칙은 한곳에 머뭅니다
나중에 여기에 지역, 기능 플래그, fallback 로직이 붙으면 팩토리의 가치가 훨씬 선명해집니다.
어떤 상황에서 팩토리가 특히 잘 맞나
생성이 사소하지 않을수록 팩토리 패턴은 더 유용해집니다.
대표적인 경우는:
- 여러 구현체 중 하나를 골라야 할 때
- 환경별로 생성 방식이 달라질 때
- 객체가 사용 가능해지기까지 초기화가 여러 단계일 때
- 서드파티 구현 세부사항을 호출자에게 숨기고 싶을 때
- concrete class가 코드베이스 전체로 퍼지는 것을 막고 싶을 때
컨트롤러, 서비스, 잡, UI 레이어 여러 곳에 생성 분기 로직이 동시에 보이기 시작하면, 대개 생성 책임이 이미 횡단 관심사가 된 것입니다.
팩토리가 결합도를 낮추는 방식
팩토리는 concrete knowledge를 한곳에 몰아 주기 때문에 결합도를 낮추는 데 도움이 됩니다.
팩토리가 없으면 사용 코드는 종종 아래를 알아야 합니다.
- concrete class 이름
- 생성자 인자
- 초기화 순서
- 어떤 조건에서 어떤 구현을 고를지
팩토리가 있으면 사용 코드는 보통 아래만 알면 됩니다.
- 자신이 기대하는 추상화
- 올바른 인스턴스를 요청하기 위한 입력
이 차이는 작아 보여도 큽니다. 나중에 생성 규칙이 바뀌어도 수정해야 할 호출자가 훨씬 줄어듭니다.
그래서 팩토리가 작은 클래스나 함수 하나뿐이더라도 유지보수성에 도움이 될 수 있습니다.
Strategy, DI와는 어떻게 다른가
이 개념들은 자주 같이 등장해서 헷갈리기 쉽습니다.
Strategy는 동작을 교체하는 데 초점이 있습니다.
- “어떤 결제 알고리즘을 쓸까?”
Factory는 올바른 객체를 생성하는 데 초점이 있습니다.
- “어떤 결제 객체를 만들어야 하지?”
Dependency Injection은 의존성을 소비자에게 어떻게 주입할지에 초점이 있습니다.
- “이 서비스는 필요한 의존성을 어떻게 전달받지?”
이 셋은 함께 일할 수 있습니다.
- 팩토리가 strategy 객체를 만들 수 있고
- DI 컨테이너가 factory를 호출할 수도 있고
- 팩토리는 concrete dependency가 사용 코드로 퍼지는 것을 막는 데 도움을 줍니다
하지만 같은 개념은 아닙니다.
팩토리와 DIP
의존성 역전 원칙(DIP)은 상위 수준 코드는 하위 수준 구현 세부보다 추상화에 더 의존해야 한다는 이야기입니다.
팩토리는 concrete choice를 한곳에 모아 두기 때문에 여기서 도움이 됩니다.
즉:
- 상위 수준 코드는 추상화만 요구하고
- 어떤 구현을 인스턴스화할지는 팩토리가 결정할 수 있습니다
그래서 팩토리는 “추상화 중심 설계”와 “현실적인 객체 wiring” 사이를 이어 주는 다리처럼 느껴질 때가 많습니다.
팩토리만 쓴다고 자동으로 DIP를 만족하는 것은 아니지만, 실제 코드에서 DIP를 적용하기는 훨씬 쉬워집니다.
팩토리에도 여러 모양이 있다
초보자 설명에서는 “팩토리 패턴”이 꼭 하나의 정해진 형태처럼 보일 때가 많습니다. 하지만 실무에서는 여러 모양이 가능합니다.
- 단순 factory 함수
- static factory class
- 좀 더 형식적인 factory interface
- 프레임워크 수준 provider나 builder
중요한 것은 의식의 양이 아니라, 생성 책임이 의미 있게 분리되었느냐입니다.
예를 들어 createPaymentProvider() 같은 평범한 함수가 문제를 가장 명확하게 풀어 준다면, 그것도 충분히 팩토리적 사고입니다.
언제는 팩토리를 쓰지 않는 편이 낫나
보통 아래 조건이면 팩토리가 굳이 필요하지 않을 수 있습니다.
- 생성이 아주 단순하고
- 구현체가 하나로 안정되어 있고
- 별다른 설정이나 분기가 없고
- 추상화를 추가하면 얻는 것보다 숨기는 것이 더 많을 때
이때는 이런 질문이 도움이 됩니다.
“객체 생성 안에 따로 모아 둘 만한 정책, 분기, 초기화 복잡성이 실제로 있는가?”
없다면 직접 생성이 가장 좋은 선택일 수 있습니다.
팩토리는 생성 자체가 지식이 된 순간에 가장 값을 합니다.
자주 하는 실수
팀들이 팩토리를 오히려 나쁘게 쓰는 경우는 보통 이렇습니다.
- 모든 클래스에 습관처럼 factory를 붙이는 것
- 단순한 생성을 쓸데없는 간접화 뒤에 숨기는 것
- 서로 무관한 도메인을 하나의 거대한 switch문 팩토리에 몰아넣는 것
- 생성 로직과 비즈니스 행동을 한 팩토리 안에 섞는 것
- 프레임워크가 있으니 생성 설계는 더 생각 안 해도 된다고 여기는 것
또 하나 흔한 오해는 팩토리가 new를 무조건 없애는 기술이라는 생각입니다.
핵심은 그게 아닙니다. 의미 없는 팩토리 wrapper보다, 좋은 직접 생성이 훨씬 더 명확할 때도 많습니다.
빠르게 보는 체크리스트
팩토리가 도움이 될지 판단할 때는 아래를 보면 좋습니다.
- 여러 구현체 중 선택이 필요하다
- 환경이나 설정이 생성 방식에 영향을 준다
- 초기화가 한 단계 이상이다
- 호출자가 concrete class를 너무 많이 알고 있다
- 생성 규칙이 여러 곳에 중복된다
- 상위 모듈이 concrete type보다 추상화에 의존해야 한다
대부분 해당하지 않으면 팩토리가 큰 가치를 주지 못할 수 있습니다.
FAQ
Q. new를 직접 쓰는 것보다 팩토리가 항상 더 좋은가요?
아닙니다. 생성이 단순하고 안정적이라면 직접 생성이 가장 좋은 선택일 때가 많습니다.
Q. 팩토리 패턴과 DI는 같은 건가요?
아닙니다. 관련은 있지만, DI는 의존성 전달에 관한 이야기이고 factory는 생성 책임에 관한 이야기입니다.
Q. 팩토리는 꼭 클래스여야 하나요?
그렇지 않습니다. 생성 로직을 명확하게 모아 주는 잘 이름 붙은 함수 하나로도 충분할 수 있습니다.
Q. 큰 시스템에서만 의미가 있나요?
복잡성이 커질수록 가치가 더 커지지만, 중간 규모 시스템에서도 생성 규칙이 퍼지기 시작하면 충분히 도움이 됩니다.
Q. 초보자는 무엇부터 보면 좋을까요?
객체 생성 분기가 여러 곳에 중복되는지, 호출자가 concrete implementation을 너무 많이 알고 있는지부터 보면 좋습니다.
Read Next
- 생성 이후 동작 교체 관점은 Strategy Pattern 가이드와 이어서 보면 좋습니다.
- 더 넓은 의존성 wiring 관점은 Dependency Injection 가이드가 잘 연결됩니다.
- 팩토리 설계 뒤의 추상화 원칙은 DIP 가이드와 함께 보면 감이 더 잘 잡힙니다.
Related Posts
먼저 읽어볼 가이드
검색 유입이 많은 핵심 글부터 이어서 보세요.
- 미들웨어 트러블슈팅 가이드: Redis, RabbitMQ, Kafka 중 어디부터 볼까 Redis, RabbitMQ, Kafka가 함께 있는 시스템에서 지금 보이는 장애가 어느 계층에 더 가까운지, 첫 10분 안에 무엇을 확인하고 어떤 글로 들어가야 하는지 정리한 실전 허브 가이드입니다.
- Kubernetes CrashLoopBackOff: 먼저 볼 것들 startup failure, probe, config, resource limit 관점에서 CrashLoopBackOff를 어떻게 나눠서 봐야 하는지 정리한 가이드입니다.
- Astro 기술 블로그 SEO 체크리스트: 트래픽 기다리기 전에 먼저 고칠 것 Astro 기술 블로그를 위한 실전 SEO 체크리스트입니다. 배포 호스트 확인, robots.txt, sitemap, canonical, hreflang, 구조화 데이터, 페이지별 메타데이터, noindex 판단, 검증 명령까지 우선순위대로 정리합니다.
- 다국어 블로그 canonical과 hreflang 설정 가이드: 무엇을 확인하고 어디서 깨질까 다국어 블로그에서 canonical과 hreflang을 어떻게 설정해야 하는지 실전 기준으로 정리합니다. self-canonical, 상호 연결되는 hreflang 묶음, x-default, 카테고리 페이지, 최종 렌더 HTML 점검, 한 언어 버전이 다른 언어 버전을 눌러버리는 실수까지 다룹니다.
- OpenAI Codex CLI 설치 가이드: 설치, 인증, 첫 작업까지 OpenAI Codex CLI를 실전 기준으로 설치하는 방법을 정리했다. 설치, 로그인, 첫 실행, Windows 주의점, 첫 작업을 어떻게 시작하면 좋은지까지 다룬다.