객체 생성을 호출 지점마다 직접 하면 호출자가 구체 클래스에 의존한다. 구현 교체 시 호출자 전체를 건드리게 된다. Factory 는 그 결합을 끊는다 — 생성 책임을 한곳에 모으고 호출자는 추상에만 의존하게 만든다.

Factory 라는 이름은 한 패턴이 아니라 세 변형을 가리킨다. Factory Method, Abstract Factory, Static Factory Method. 자주 한 묶음으로 다뤄지지만 의도와 적용이 다르다. 같은 이름 아래 의도가 갈리는 패턴이다.

공통 의도

세 변형이 공유하는 의도는 객체 생성과 사용의 분리다.

직접 생성하는 코드는 호출자가 구체 클래스를 안다는 뜻이다. new MySQLConnection() 같은 호출은 호출자가 MySQLConnection 이라는 클래스를 안다는 의미고, PostgreSQL 로 교체하려면 모든 호출자를 수정해야 한다. Factory 는 그 호출을 추상화한다. 호출자는 Connection 같은 인터페이스에만 의존하고, 어떤 구체 구현을 받을지는 Factory 가 결정한다.

여기까지가 셋의 공통점이다. 분리의 방식 에서 갈래가 나뉜다.

Factory Method

Factory Method 는 객체 생성을 서브클래스에 위임한다.

부모 클래스에 추상 메서드 createProduct() 를 두고, 서브클래스가 그 메서드를 구현해서 구체 클래스를 결정한다. 부모 클래스의 다른 메서드들은 그 추상 메서드의 결과를 사용한다. 호출 흐름 전체가 부모에 있고, 한 가지 결정만 서브클래스로 위임되는 구조다. Template Method 의 한 형태로 봐도 무방하다.

상속 기반이라는 점이 가장 큰 특징이다. 새 구체 클래스가 필요하면 새 서브클래스를 만들어야 한다. 도메인이 안정되고 확장 지점이 명확할 때 적합하다. 반대로 확장 지점이 자주 바뀌거나 다중 상속이 곤란한 언어에서는 부담이 크다.

JDK 의 Collection.iterator() 가 전형이다. Collection 인터페이스가 Iterator 생성을 추상으로 정의하고, ArrayList, HashSet 같은 구현체가 자기에게 맞는 Iterator 를 반환한다.

Abstract Factory

Abstract Factory 는 관련된 객체 family 의 일관 생성을 다룬다.

한 인터페이스에 여러 product 의 생성 메서드를 둔다. 예를 들어 UIFactorycreateButton(), createWindow(), createScrollbar() 를 묶어서 정의하고, MacUIFactory 는 Mac 스타일 UI 객체들을, WindowsUIFactory 는 Windows 스타일 객체들을 반환한다. 호출자는 한 family 안의 객체들이 서로 어울린다 는 보장을 받는다.

DB 드라이버도 같은 유형의 사례다. DriverFactorycreateConnection(), createStatement(), createResultSet() 을 묶고, 각 DB 별 Factory 가 자기 family 의 객체들을 일관되게 반환한다.

여러 product 가 짝을 이뤄야 의미가 있을 때 적합하다. 단일 객체 생성을 추상화하는 데는 과한 도구다. 그리고 family 에 새 product 가 추가되면 모든 Factory 구현체를 수정해야 한다는 제약이 있다. 도메인이 안정되고 product 종류가 잘 정의된 상황에 맞는다.

Static Factory Method

Static Factory Method 는 생성자의 한계를 보완하는 정적 메서드다. Effective Java 의 Item 1 이 정리한 패턴.

생성자는 네 가지 한계를 갖는다. 이름이 없다. 같은 시그니처로 여러 생성자를 만들 수 없다. 호출할 때마다 새 인스턴스를 반환해야 한다. 정확한 반환 타입을 호출자가 알아야 한다. Static Factory Method 는 이 네 가지를 모두 해결한다.

  • 이름 있는 생성BigInteger.probablePrime() 같은 호출은 무엇을 생성하는지 이름이 알려준다. 생성자 오버로딩으로는 같은 의미를 전달하기 어렵다.
  • 캐싱Integer.valueOf(int) 는 자주 쓰이는 값 (-128 ~ 127) 을 캐싱해서 같은 인스턴스를 반환한다. Flyweight 패턴의 기반이다.
  • 반환 타입 다양화 — 인터페이스를 반환 타입으로 선언하고 실제로는 구현체를 반환한다. Collections.unmodifiableList() 가 반환하는 객체의 구체 클래스를 호출자는 모른다. 구현이 바뀌어도 호출자는 영향받지 않는다.
  • 반환 객체 클래스가 호출 시점에 존재하지 않아도 됨 — JDBC 의 DriverManager.getConnection() 이 대표다. 호출 시점에 어떤 Driver 가 로딩되어 있느냐에 따라 다른 클래스의 인스턴스가 반환된다.

자바 표준 라이브러리만 봐도 Optional.of, List.of, Map.of, Stream.of, Files.newBufferedReader 같은 메서드가 모두 같은 패턴이다. 실무에서 가장 자주 마주치는 변형이다.

한계도 있다. 정적 메서드는 상속이 어렵다 — protected 가 없으니 서브클래스에서 재정의할 수 없다. 그리고 메서드 이름이 직관적이지 않으면 생성자보다 발견이 어렵다. 관례로 of, from, valueOf, getInstance, newInstance 같은 이름을 자주 쓴다.

선택 기준

세 변형의 결을 따라 적용 조건을 정리하면 다음과 같다.

  • 같은 시그니처로 여러 구체 구현이 필요한가 — Factory Method (상속). 도메인이 안정되고 확장 지점이 명확할 때.
  • 관련 객체들이 짝을 이뤄야 하는가 — Abstract Factory (조합). UI family, DB 드라이버 family 처럼 product 들이 함께 일관성을 가질 때.
  • 생성자의 한계 (이름 없음, 캐싱 불가, 구체 타입 노출) 가 문제인가 — Static Factory Method (대안). 실무에서 가장 흔한 선택.

세 변형은 배타적이지 않다. 한 라이브러리 안에 셋이 모두 등장하는 경우도 많다.

DI 컨테이너와의 관계

DI 컨테이너는 Factory 의 일반화로 볼 수 있다.

컨테이너는 객체 생성과 의존성 주입을 모두 담당한다. 어떤 구현을 어디에 주입할지는 설정으로 결정되고, 호출자는 추상 (인터페이스) 에만 의존한다. Abstract Factory 가 product family 의 일관 생성을 담당하던 역할을 컨테이너가 같이 맡는다. Static Factory Method 의 캐싱은 컨테이너의 singleton scope 와 같은 효과를 낸다.

그렇다면 명시적 Factory 가 사라지는가. 단순한 생성에서는 컨테이너로 충분하지만, 도메인 로직에 따라 다른 구체를 생성해야 하는 경우에는 명시적 Factory 가 여전히 더 명확하다. 결제 수단에 따라 다른 PaymentProcessor 를 만들거나, 사용자 등급에 따라 다른 DiscountPolicy 를 만드는 경우가 그렇다. 컨테이너 설정은 정적 결정이고, 도메인 의존 분기는 런타임 결정이다.

dependency-injection 글에서 다룬 DIP 가 이 분리를 가능하게 한 추상이고, Factory 는 그 추상을 코드로 표현하는 방식 중 하나다.

결론

Factory 라는 이름 아래 세 변형이 모이지만 결정 축은 다르다. Factory Method 는 상속을 통한 확장, Abstract Factory 는 객체 family 의 일관성, Static Factory Method 는 생성자의 한계 보완. 같은 의도 (생성과 사용의 분리) 를 다른 방식으로 풀어낸다.

실무에서 가장 자주 마주치는 변형은 Static Factory Method 다. 자바 표준 라이브러리부터 도메인 코드까지 같은 패턴이 반복된다. Factory Method 와 Abstract Factory 는 특정 조건 (상속 가능 / 객체 family 존재) 에 부합할 때 선택한다.

세 변형의 이름이 비슷해서 한 패턴으로 묶이지만, 적용 결정은 의도에 맞춰 갈래를 나눠 봐야 한다.

참고

  • Singleton — Static Factory Method 의 getInstance 가 단일 인스턴스 보장에 쓰이는 지점
  • Dependency Injection — DIP, IoC, DI의 위계 — DI 컨테이너가 Factory 의 일반화인 맥락
  • Joshua Bloch — Effective Java (3rd ed.), Item 1: Consider static factory methods instead of constructors
  • GoF — Design Patterns: Elements of Reusable Object-Oriented Software (1994)