공부/디자인 패턴

디자인 패턴 - 추상 팩토리 패턴(Abstract Factory)

dev_jiwonpark 2025. 6. 28. 20:09
추상 팩토리란?
서로 관련 있는 객체들(제품군)을 생성할 수 있는 팩토리들을 묶어서,
클라이언트가 구체적인 클래스에 의존하지 않고 제품군을 통일된 방식으로 만들 수 있도록 해주는 생성 패턴

 

이번에 추상 팩토리 패턴에 대해서 작성해보려고 한다.
또한 팩토리 메서드 패턴과 다른점이 무엇인지도 같이 다뤄보려고 한다! 

https://dev-jiwonpark.tistory.com/24

 

디자인패턴 - 팩토리 메서드 패턴

추상 팩토리 패턴을 공부하다가.. 너무너무 이해가 안되는거다..도대체 이게 뭔데!? 를 반복하다가 팩토리 메서드 패턴을 먼저 공부해야 이해가 될것 같아 팩토리 메서드 패턴부터 공부해보려고

dev-jiwonpark.tistory.com

 

팩토리 메서드 패턴에서 다뤘던 비유는 커피 머신 이었다. 

그럼 추상 팩토리 머신은 어떻게 비유할 수 있을까?? 

팩토리 메서드는 " 커피 한 잔 " 만 만들었다면 이제 손님이 커피 + 디저트 세트를 달라고 하는 것 과 같다.

그리고 그 원하는 세트 스타일은 아래 처럼 다양하다.

아메리카노 세트 Americano Cookie
라떼 세트 Latte Muffin
바닐라라떼 세트 VanillaLatte Cake

 

위처럼 커피와 디저트가 서로 어울리도록 같은 세트 스타일로 만들어줘야 한다는 것이다.

이를 해결해줄 수 있는게 추상 팩토리 패턴이다!

 

미리 요약하자면..!

(1) 커피와 디저트를 묶어서 같은 스타일로 만들어주는 팩토리를 추상화한다.

(2) 클라이언트는 createCoffee()와 createDessert()만 호출하면 된다.

(3) 어떤 세트인지(아메리카노/라떼/바닐라)는 팩토리만 바꾸면 된다.

위의 개념을 이해할 수 있도록 아래에 예시를 보면서 같이 공부해보면 좋을 것 같다. 

// abstract-factory-cafe.ts

// 1. 제품 인터페이스들
interface Coffee {
  prepare(): string;
}

interface Dessert {
  serve(): string;
}

// 2. 구체적인 커피들
class Americano implements Coffee {
  prepare(): string {
    return "아메리카노를 준비합니다: 에스프레소 + 물";
  }
}

class Latte implements Coffee {
  prepare(): string {
    return "라떼를 준비합니다: 에스프레소 + 우유";
  }
}

// 3. 구체적인 디저트들
class Cookie implements Dessert {
  serve(): string {
    return "쿠키를 제공합니다";
  }
}

class Muffin implements Dessert {
  serve(): string {
    return "머핀을 제공합니다";
  }
}

// 4. 추상 팩토리
interface CafeSetFactory {
  createCoffee(): Coffee;
  createDessert(): Dessert;
}

// 5. 구체 팩토리들
class AmericanoSetFactory implements CafeSetFactory {
  createCoffee(): Coffee {
    return new Americano();
  }

  createDessert(): Dessert {
    return new Cookie();
  }
}

class LatteSetFactory implements CafeSetFactory {
  createCoffee(): Coffee {
    return new Latte();
  }

  createDessert(): Dessert {
    return new Muffin();
  }
}

// 6. 클라이언트 코드
function orderCafeSet(factory: CafeSetFactory) {
  const coffee = factory.createCoffee();
  const dessert = factory.createDessert();
  console.log(coffee.prepare());
  console.log(dessert.serve());
}

// 7. 실행
orderCafeSet(new AmericanoSetFactory());
orderCafeSet(new LatteSetFactory());

위 예제를 실행한다면 다음과 같은 결과를 확인할수있다.


하나하나 구조를 뜯어보면서 이해해 보려고 한다.

 

(1) 제품군의 추상 인터페이스

interface Coffee {
  prepare(): string;
}

interface Dessert {
  serve(): string;
}

커피든 디저트든 "어떻게 만들어지는지는 몰라도", 모두 prepare, serve 메서드를 가진다.

 

(2) 추상 인터페이스의 구현체들

class Americano implements Coffee {
  prepare(): string {
    return "아메리카노를 준비합니다";
  }
}

 인터페이스는 실제 동작이 없기 때문에, 현실에서 사용할 수 있는 구현체가 필요하다.

 

(3) 제품군 생성을 위한 추상 팩토리

interface CafeSetFactory {
  createCoffee(): Coffee;
  createDessert(): Dessert;
}

어떤 구체적인 커피와 디저트를 만드는지는 몰라도 되고, 커피와 디저트를 바꿔도 코드는 바뀌지 않게된다. 

 

(4) 실제 팩토리

class AmericanoSetFactory implements CafeSetFactory {
  createCoffee() {
    return new Americano();
  }
  createDessert() {
    return new Cookie();
  }
}

스타일 별로 제품군(커피 + 디저트)를 묶어서 생성한다.

이걸로 일관된 조합을 보장할 수 있다.

 

(5) 클라이언트 코드

function orderCafeSet(factory: CafeSetFactory) {
  const coffee = factory.createCoffee();
  const dessert = factory.createDessert();
  console.log(coffee.prepare());
  console.log(dessert.serve());
}

어던 스타일의 세트를 고를지 팩토리만 바꾸면 끝이다.

클라이언트는 new Americano() 나 new Muffin()을 직접 호출할 필요가 없다.

그래서 클라이언트는 어떤 커피를 어떤 디저트를 만들지 직접 결정하지 않고 팩토리를 호출한다.

 

즉, 팩토리를 통해 일관된 방식으로 세트를 생성할 수 있게된다.

만약 Latte 세트를 시켰는데 커피는 Latte를 시켰는데 디저트가 Muffin이 아니라 Cookie면 

이건 그냥 스타일이 뒤섞인거고 추상 팩토리가 없다면 이런 조합이 실수로 생길수 있는거다.

그래서 추상팩토리는 Latte + Muffin, Americano + Cookie 이런 " 쌍 " 을 공장 단위로 책임지게 만들어서 

제품군 간 일관성을 보장하는 것이다.

 

이런 구조를 가진 추상 팩토리 패턴이 없다면 어떤 문제점이 발생할까...?

만약 클라이언트가 new를 직접 사용했다면?

function orderCafeSet(style: string) {
  let coffee: Coffee;
  let dessert: Dessert;

  if (style === "americano") {
    coffee = new Americano();
    dessert = new Cookie();
  } else if (style === "latte") {
    coffee = new Latte();
    dessert = new Muffin();
  } else {
    throw new Error("알 수 없는 스타일");
  }

  console.log(coffee.prepare());
  console.log(dessert.serve());
}

- if-else 로 스타일 마다 조건문이 작성되며

- 새로운 스타일이 추가되면 클라이언트 코드가 수정되어야한다.(OCP 위반)

- 테스트, 유지보수, 확장이 어렵다.

 

하지만 추상 팩토리가 있다면 orderCafeSet(factory: CafeSetFactory) 이 코드 자체는 안바뀌고 

CappuccinoSetFactory 만 새로 만들면 끝이다.

 

위 구조를 UML로 도식화 하면 아래와 같다. 

UML을 요약하자면 아래와 같다!

1. 추상 제품 인터페이스 (제품군 정의)

(1) Coffee : 모든 커피 클래스가 따라야 할 인터페이스

-> prepare(): string

(2) Dessert : 모든 디저트 클래스가 따라야 할 인터페이스

-> serve() : string

 

2. 구체 제품 클래스 (제품군 구현)

(1) Americano, Latte → Coffee를 구현

(2) Cookie, Muffin → Dessert를 구현

 

3. 추상 팩토리 (제품 세트를 만드는 계약서)

CafeSetFactory:

-> createCoffee(): Coffee

-> createDessert(): Dessert

: 커피와 디저트를 한 세트로 만드는 인터페이스 

 

4. 구체 팩토리 (세트 스타일에 따라 객체 생성)

(1) AmericanoSetFactory:
→ createCoffee() → Americano
→ createDessert() → Cookie

(2) LatteSetFactory:
→ createCoffee() → Latte
→ createDessert() → Muffin

 

5. 클라이언트 코드

orderCafeSet(factory: CafeSetFactory)
→ 팩토리만 전달받아서
→ createCoffee()와 createDessert()를 호출
→ 커피와 디저트를 일관된 세트로 생성

 

Reference

https://refactoring.guru/ko/design-patterns/abstract-factory

 

추상 팩토리 패턴

/ 디자인 패턴들 / 생성 패턴 추상 팩토리 패턴 다음 이름으로도 불립니다: Abstract Factory 의도 추상 팩토리는 관련 객체들의 구상 클래스들을 지정하지 않고도 관련 객체들의 모음을 생성할 수 있

refactoring.guru

https://patterns-dev-kr.github.io/design-patterns/factory-pattern/

 

Factory 패턴

📜 원문: patterns.dev - factory pattern 팩토리 패턴을 사용하면 함수를 호출하는 것으로 객체를 만들어낼 수 있다. new 키워드를 사용하는 대신 함수 호출의 결과로 객체를 만들 수 있는 것이다. 앱에

patterns-dev-kr.github.io