객체의 생성(create) 과정을 전담하는 클래스(공장)를 만들어, 객체 생성을 사용하는 코드로부터 분리/추상화하는 디자인 패턴입니다.
팩토리(Factory), 즉 '공장'이라는 이름처럼, 객체를 직접 new로 만드는 것이 아니라 '공장'에 요청하여 객체를 받아오는 방식입니다.
- 핵심 정의: 객체를 사용하는 코드(클라이언트)에서 객체 생성 코드를 분리(추상화)하는 패턴입니다.
- 패턴 방식: 상속 관계에 있는 두 클래스에서, 상위 클래스(추상 클래스)가 인터페이스와 같은 중요한 뼈대를 결정하고, 하위 클래스(구현 클래스)가 객체 생성에 관한 구체적인 내용을 결정합니다.
👍 팩토리 패턴의 장점
- 느슨한 결합 (Loose Coupling): 객체를 사용하는 상위 클래스와 객체를 생성하는 하위 클래스가 분리됩니다. 이로 인해 상위 클래스는 어떤 구체적인 인스턴스가 생성되는지 전혀 알 필요 없이 약속된 '뼈대'(인터페이스)만 보고 작동하므로 유연성이 극대화됩니다.
- 유지보수성 증가: 객체를 생성하는 모든 로직이 '공장(Factory)' 한곳에 중앙화되어 있습니다. 만약 객체 생성 방식(예: 생성자 변경)을 수정해야 할 때, 코드를 사용하는 모든 곳을 찾아다닐 필요 없이 딱 한 곳, 즉 해당 팩토리만 수정하면 되므로 유지보수성이 매우 높아집니다.
💻 코드 예시
1. 자바스크립트(JavaScript)의 팩토리 메서드 패턴
이 예시는 상위 CoffeeFactory를 상속받은 하위 팩토리(LatteFactory 등)가 구체적인 객체 생성을 담당하는 "팩토리 메서드" 패턴에 가깝습니다.
// 상위 클래스 (뼈대)
class CoffeeFactory {
// static: CoffeeFactory.createCoffee()로 바로 호출 가능 (공장 가동)
static createCoffee(type) {
// 요청(type)에 맞는 구체적인 하위 공장(factory)을 찾음
const factory = factoryList[type];
// 해당 하위 공장에 실제 생산을 위임(delegate)
return factory.createCoffee();
}
}
// --- 실제 생산될 객체 (제품) ---
class Latte {
constructor() {
this.name = "latte";
}
}
class Espresso {
constructor() {
this.name = "Espresso";
}
}
// --- 하위 클래스 (구체적인 생산 공장) ---
class LatteFactory extends CoffeeFactory {
static createCoffee() {
return new Latte(); // 라떼 공장은 라떼만 생산
}
}
class EspressoFactory extends CoffeeFactory {
static createCoffee() {
return new Espresso(); // 에스프레소 공장은 에스프레소만 생산
}
}
// 공장 목록 (어떤 요청에 어떤 공장을 돌릴지)
const factoryList = { LatteFactory, EspressoFactory };
// --- 클라이언트 코드 (손님) ---
const main = () => {
// 손님(main)은 그저 상위 공장(CoffeeFactory)에 요청할 뿐,
// LatteFactory가 작동하는지, new Latte()가 호출되는지 모름.
const coffee = CoffeeFactory.createCoffee("LatteFactory");
console.log(coffee.name); // latte
};
main();
분석:
- CoffeeFactory라는 상위 클래스(추상 공장)가 createCoffee라는 중요한 뼈대를 결정하고, 하위 클래스인 LatteFactory가 구체적인 new Latte() 생성을 결정합니다.
- 이는 의존성 주입(DI)과는 약간 다릅니다. DI는 외부에서 생성된 객체를 주입(삽입)받는 것이고, 이 코드는 상위 팩토리가 하위 팩토리에게 생성을 위임(Delegate)하는 구조입니다. 둘 다 느슨한 결합을 추구한다는 공통점은 있습니다.
- static 키워드 (정적 메서드): static으로 선언된 메서드는 클래스의 인스턴스(객체)가 아닌 클래스 자체에 속합니다. 따라서 new CoffeeFactory()처럼 객체를 만들지 않고도 CoffeeFactory.createCoffee()로 바로 호출할 수 있습니다. 프로그램 실행 시 메모리에 딱 한 번만 할당되므로, 유틸리티 함수나 팩토리 메서드처럼 객체의 상태와 관계없이 기능만 제공할 때 사용하기 좋습니다.
2. 자바(Java)의 팩토리 메서드 패턴
이 예시는 상속 대신, 하나의 중앙화된 팩토리 클래스가 switch 문을 통해 모든 객체 생성을 처리하는 "단순 팩토리(Simple Factory)" 패턴입니다.
// Enum: 커피 타입을 '상수 집합'으로 안전하게 관리
enum CoffeeType {
LATTE,
ESPRESSO
}
// Abstract Class (추상 클래스): 커피라는 제품의 기본 뼈대(설계도)
// new Coffee()는 불가능함.
abstract class Coffee {
protected String name;
public String getName() {
return name;
}
}
// --- 실제 제품 클래스 ---
class Latte extends Coffee {
public Latte() {
name = "latte"; // 설계도를 구체화
}
}
class Espresso extends Coffee {
public Espresso() {
name = "Espresso"; // 설계도를 구체화
}
}
// --- 중앙 객체 생성 공장 ---
class CoffeeFactory {
// 이 공장은 요청(type)만 받으면 알아서 적절한 객체를 생산(switch)함
public static Coffee createCoffee(CoffeeType type) {
switch (type) {
case LATTE:
return new Latte();
case ESPRESSO:
return new Espresso();
default:
// 잘못된 요청이 들어오면 예외 발생
throw new IllegalArgumentException("Invalid coffee type: " + type);
}
}
}
// --- 클라이언트 코드 (손님) ---
public class Main {
public static void main(String[] args) {
// 손님(Main)은 CoffeeFactory에 LATTE 타입만 요청함.
// new Latte() 코드는 공장 내부에 숨겨져 있음(캡슐화).
Coffee coffee = CoffeeFactory.createCoffee(CoffeeType.LATTE);
System.out.println(coffee.getName()); // latte
}
}
분석 및 용어 설명:
- Enum (열거형 타입):
- LATTE, ESPRESSO처럼 서로 연관된 상수의 집합을 정의할 때 사용하는 특별한 타입입니다.
- 장점 1 (안정성): 문자열("Latte")이나 숫자(1) 대신 CoffeeType.LATTE라는 명확한 타입을 사용하므로, 컴파일 시점에 오류를 잡을 수 있고 코드 가독성이 올라갑니다.
- 장점 2 (유지보수): 새로운 커피 타입(예: AMERICANO)을 추가할 때, 다른 파일을 건드릴 필요 없이 Enum 정의부와 팩토리의 switch문에 case만 추가하면 됩니다.
- 장점 3 (스레드 세이프): Enum의 인스턴스는 JVM에 의해 단 하나만 생성되는 것이 보장됩니다. 이 특성 덕분에 Enum은 싱글톤 패턴을 구현하는 가장 간편하고 안전한(Thread-Safe) 방법 중 하나로 사용됩니다.
- abstract class (추상 클래스):
- '미완성 설계도'입니다. Coffee 클래스처럼, getName()같은 공통 기능은 구현해 두지만, 스스로는 new Coffee()를 통해 객체화될 수 없습니다. 반드시 Latte나 Espresso처럼 상속을 통해 세부 내용이 완성되어야만 하는 클래스를 의미합니다.
1. 팩토리 패턴에 대해 설명해주세요.
팩토리 패턴이란 클라이언트에서 객체 생성 코드를 분리하는 패턴입니다. 상속 관계에 있는 두 클래스에서 상위 클래스가 인터페이스와 같은 중요한 뼈대를 결정하고, 하위 클래스가 구체적인 내용을 결정합니다. 이를 통해 클라이언트 코드의 변경 없이 새로운 종류의 객체를 쉽게 추가할 수 있어, 시스템의 유연성과 확장성을 높이는 데 큰 장점이 있습니다.
'Computer Science > Design Pattern' 카테고리의 다른 글
| ⛓️ 프록시 패턴과 프록시 서버 (1) | 2025.09.16 |
|---|---|
| 📡 옵저버 패턴 (Observer Pattern) (0) | 2025.09.09 |
| ⚔️ 전략 패턴 (Strategy Pattern) (0) | 2025.09.09 |
| 💡 싱글톤 패턴 (Singleton Pattern) (0) | 2025.09.09 |
| 👨💻 디자인 패턴과 주요 원칙 (0) | 2025.09.09 |