cv.cm

코딩 · 디자인 패턴

예제로 이해하는 추상 팩토리 패턴

7분 분량

핵심 답변

추상 팩토리 패턴은 구체 클래스를 지정하지 않고 서로 관련된 객체의 제품군을 만드는 인터페이스를 제공하는 생성 패턴입니다. 클라이언트 코드는 LightThemeFactory나 DarkThemeFactory 같은 팩토리 객체를 받아 각 제품(버튼, 대화상자)을 요청합니다. 하나의 팩토리가 제품군 전체를 만들기 때문에 제품끼리 항상 서로 어울리고, 다른 팩토리를 넘기기만 하면 제품군을 바꿀 수 있습니다.

의도

구체 클래스를 지정하지 않고 서로 관련되거나 의존적인 객체들의 제품군을 만드는 인터페이스를 제공합니다. 추상 팩토리 패턴은 Gang of Four(GoF) 책의 다섯 가지 생성 패턴 중 하나이며, 책에서는 Kit이라고도 부릅니다.

해결하는 문제

웹 페이지와 일반 텍스트 터미널, 두 대상에 출력을 렌더링할 수 있는 보고서 앱을 떠올려 보세요. 각 대상에는 제목, 표, 경고 상자 같은 여러 구성 요소가 각자의 버전으로 필요합니다. 웹용 제목을 터미널용 표와 섞으면 출력이 깨집니다.

보고서 코드가 new HtmlHeading()과 new TextTable()을 직접 호출하면 구체 클래스에 묶이고, 대상 선택이 여러 줄에 흩어지며, 누군가 제품군을 섞는 것을 막을 방법이 없습니다. 추상 팩토리 패턴은 한 제품군의 생성을 모두 하나의 객체 뒤에 두어, 선택이 정확히 한곳에서만 이루어지게 합니다.

구조와 참여자

참여자역할예제에서
AbstractFactory제품 타입마다 생성 메서드를 하나씩 선언.UiFactory
ConcreteFactory한 제품군의 생성 메서드를 구현.HtmlFactory, TextFactory
AbstractProduct한 종류의 제품에 대한 인터페이스.Heading, Alert
ConcreteProduct특정 제품군에 속한 제품.HtmlHeading, TextAlert
Client추상 팩토리와 추상 제품 인터페이스만 사용.renderReport

TypeScript 구현

interface Heading { render(text: string): string; }
interface Alert { render(text: string): string; }

interface UiFactory {
  createHeading(): Heading;
  createAlert(): Alert;
}

class HtmlFactory implements UiFactory {
  createHeading(): Heading { return { render: (t) => `<h1>${t}</h1>` }; }
  createAlert(): Alert { return { render: (t) => `<div class="alert">${t}</div>` }; }
}

class TextFactory implements UiFactory {
  createHeading(): Heading { return { render: (t) => `${t}\n${"=".repeat(t.length)}` }; }
  createAlert(): Alert { return { render: (t) => `[!] ${t}` }; }
}

// The client knows only the interfaces.
function renderReport(ui: UiFactory): string {
  const heading = ui.createHeading();
  const alert = ui.createAlert();
  return [heading.render("Sales"), alert.render("2 orders failed")].join("\n");
}

const target: string = "text"; // e.g. from a command-line flag
const factory: UiFactory = target === "html" ? new HtmlFactory() : new TextFactory();
console.log(renderReport(factory));
// Sales
// =====
// [!] 2 orders failed

구체 클래스의 이름이 나오는 곳은 팩토리를 고르는 줄뿐입니다. renderReport는 어떤 제품군과도 동작하며, 모든 제품을 같은 팩토리에서 받으므로 HTML과 텍스트 조각을 실수로 섞을 수도 없습니다.

실제 프로그램에서는 제품이 보통 메서드가 더 많은 클래스입니다. 예제를 짧게 하려고 객체 리터럴을 썼을 뿐 구조는 같습니다.

언제 쓰고 언제 쓰지 말아야 하나

다음과 같을 때 추상 팩토리 패턴을 사용합니다.

  • 시스템이 서로 관련된 제품의 여러 제품군과 함께 동작해야 하고, 서로 다른 제품군의 제품을 조합하면 안 될 때(UI 테마, 운영체제 위젯, 짝이 맞는 연결 객체와 명령 객체를 가진 데이터베이스 드라이버).
  • 제품군을 설정 시점에 한 번만 고르고 나머지 코드는 그것과 무관하게 유지하고 싶을 때.
  • 테스트에서 하나의 팩토리로 가짜 객체 제품군을 공급하고 싶을 때.

다음 경우에는 피하세요.

  • 제품군이 하나뿐일 때. 직접 new를 쓰거나 팩토리 함수 하나면 더 단순합니다.
  • 제품 타입이 자주 바뀔 때. createTable() 같은 새 제품 종류를 추가하려면 팩토리 인터페이스와 모든 구체 팩토리를 바꿔야 합니다. GoF 책이 꼽는 주된 약점입니다.

추상 팩토리 패턴과 팩토리 메서드 패턴 비교

관점팩토리 메서드추상 팩토리
만드는 것제품 하나서로 관련된 제품군
방식서브클래스가 생성 메서드 하나를 오버라이드클라이언트가 생성 메서드가 여럿인 팩토리 객체를 전달받음
기반 기술상속합성
추가하기 쉬운 것새 제품 변형(새 서브클래스)새 제품군(새 팩토리 클래스)
추가하기 어려운 것거의 없음; 각 creator가 독립적새 제품 타입(모든 팩토리가 바뀜)

둘은 자주 함께 쓰입니다. 구체 팩토리의 각 생성 메서드가 그 자체로 팩토리 메서드인 경우입니다.

실제 사용 사례

  • Java XML API: DocumentBuilderFactory와 TransformerFactory는 추상 클래스입니다. newInstance()가 구체 구현을 찾아 주고, 이를 사용하는 코드는 추상 타입에만 의존합니다.
  • Java의 JDBC API도 비슷하게 동작합니다. 데이터베이스 드라이버가 짝이 맞는 Connection, Statement, ResultSet 구현을 제공하고 애플리케이션 코드는 인터페이스만 사용합니다.
  • GoF 책의 예제는 여러 룩앤필 표준을 지원하는 사용자 인터페이스 툴킷으로, 표준마다 하나의 팩토리가 어울리는 스크롤 바, 창, 버튼을 만듭니다.

관련 패턴

  • 팩토리 메서드 패턴: 구체 팩토리는 흔히 생성 메서드를 팩토리 메서드로 구현합니다.
  • 프로토타입 패턴: 팩토리에 프로토타입 객체를 설정해 두고 복사로 제품을 만들 수 있어, 제품군마다 팩토리 서브클래스를 둘 필요가 없습니다.
  • 싱글턴 패턴: 애플리케이션에는 보통 구체 팩토리마다 인스턴스 하나만 있으면 됩니다.
  • 빌더 패턴: 이것도 생성 과정을 숨기지만 복잡한 객체 하나를 단계별로 만드는 반면, 추상 팩토리는 완성된 제품을 즉시 반환합니다.
  • 브리지 패턴: 추상 팩토리로 브리지의 구현부를 만들고 설정할 수 있습니다.

자주 묻는 질문

제품군이란 무엇인가요?

한 UI 테마의 버튼, 메뉴, 대화상자처럼 함께 쓰도록 설계된 객체들의 집합입니다. 서로 다른 제품군의 제품을 섞으면 일관성이 없거나 깨진 결과가 나옵니다.

추상 팩토리에 새 제품 타입을 추가하려면 어떻게 하나요?

팩토리 인터페이스에 생성 메서드를 추가하고 모든 구체 팩토리에서 구현해야 합니다. 그래서 제품 타입의 집합이 안정적일 때 이 패턴이 가장 잘 맞습니다.

추상 팩토리 패턴은 의존성 주입과 같은가요?

아니지만 함께 쓰면 잘 어울립니다. 의존성 주입은 객체를 바깥에서 공급하는 것이고, 추상 팩토리는 클라이언트가 서로 관련된 객체 여러 개를 직접 만들어야 할 때 주입할 수 있는 종류의 객체입니다.

추상 팩토리를 클래스 대신 함수로 만들 수 있나요?

네. TypeScript에서는 팩토리 인터페이스를 만족하는 생성 함수들의 일반 객체가 팩토리가 될 수 있습니다. 이 패턴의 핵심은 클래스가 아니라 인터페이스와 제품군입니다.

코딩 튜토리얼 전체