| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | 7 |
| 8 | 9 | 10 | 11 | 12 | 13 | 14 |
| 15 | 16 | 17 | 18 | 19 | 20 | 21 |
| 22 | 23 | 24 | 25 | 26 | 27 | 28 |
| 29 | 30 | 31 |
Tags
- Binary Lifting
- ccw 알고리즘
- DP
- 이분 탐색
- trie
- Express.js
- 비트필드를 이용한 dp
- 최소 공통 조상
- 강한 연결 요소
- R 그래프
- 게임 서버 아키텍처
- Github
- Lock-free Stack
- localstorage
- Behavior Design Pattern
- 그래프 탐색
- Strongly Connected Component
- Prisma
- PROJECT
- JavaScript
- SCC
- map
- 벨만-포드
- 2-SAT
- Spin Lock
- 자바스크립트
- 비트마스킹
- 트라이
- Delete
- reference counting
Archives
- Today
- Total
dh_0e
[Design Pattern] Creational Design Patterns II (Factory Method Pattern, Enum Factory Pattern) 본문
Software Analysis & Design/Design Patterns
[Design Pattern] Creational Design Patterns II (Factory Method Pattern, Enum Factory Pattern)
dh_0e 2025. 11. 2. 17:30팩토리 메서드 패턴(Factory Method Pattern)
- 객체 생성을 서브 클래스에 위임하는 생성(Creational) 패턴
- 생성 패턴: 객체 생성 메커니즘을 캡슐화하여 객체가 생성되거나 변경되어도 프로그램 구조에 큰 영향을 주지 않도록 유연성을 제공하는 패턴

- 객체를 생성하기 위한 인터페이스는 상위 클래스에서 정의되지만, 실제로 어떤 클래스의 인스턴스를 생성할지는 서브 클래스가 결정함
- 사용 상황
- 객체의 생성을 코드의 나머지 부분으로부터 분리하려고 할 때
- 사용자가 자체 객체를 만들어 시스템을 확장하는 방법을 제공하고자 할 때
- 객체 생성에 있어서 기존의 코드를 건들지 않고 쉽게 확장할 수 있는 방법이 필요할 때 (OCP: 개방-폐쇄 원칙)
- ex) ↓ 배를 만드는 공장(ShipFactory)에서 client의 요청에 맞추어 배를 주문받아(orderShip) 배(Ship)의 객체를 주는 상황


- ShipFactory가 구체 규칙(화이트/블랙 색, 어떤 Ship을 만들지)을 직접 알고 있어 Ship과 강하게 결합됨
- 새로운 배 타입(blueship)이 추가 시 if/else문을 뜯어고쳐야 함 >> OCP 위반
- Ship 클래스에 새로운 멤버 변수가 추가(필드가 늠)되면 ShipFactory 코드도 함께 수정되어 변경 파급이 큼
팩토리 메서드 패턴의 구조
- 팩토리 메서드 패턴은 인터페이스를 통해 팩토리 객체와 제품 객체 간의 결합을 느슨하게 해줌
- 팩토리 클래스는 구체 팩토리 클래스(서브 클래스)의 상속 대상이지 제품 객체를 생성하지는 않음
- 이를 상속하는 서브 클래스의 메서드에서 여러 제품 객체 생성을 각각 책임짐

- Creator(추상 팩토리): 제품 객체를 생성하는 추상 메서드 $createProduct()$를 정의 (구체 팩토리의 상속 대상)
- ConcreteCreator(구체 팩토리): $createProduct()$ 메서드를 구현하여 특정 ConcreteProduct 객체를 반환 (제품 객체 생성)
- Product(추상 제품): 생성될 객체들의 인터페이스를 정의 (구체 제품의 상속 대상)
- ConcreteProduct(구체 제품): Product 인터페이스를 구현하는 실제 제품 객체

- ConcreteProductA에 대한 구체적인 생성은 ConcreteCreatorA(팩토리) 객체가 전담
- Client는 ConcreteCreatorA를 통해서 Product 객체를 전달받음
- ConcreteProductA product = new ConcreteCreatorA().createProduct()
ex) ShipFactory에 Factory Method Pattern을 적용하여 코드 수정


실행 흐름(Client 기준)
- Client의 main에서 new WhiteShipFactory().orderShip("whiteship") 호출
- orderShip 템플릿이 실행
- $validate("whiteship")$로 인자 검증
- $prepareFor("whiteship")$로 준비 메시지 출력
- $createShip()$ 호출 >> WhiteShipFactory가 오버라이드한 createShip에서 new WhiteShip() 반환
- 반환된 Ship이 클라이언트로 전달됨
ex) BlackShip 추가



Factory Method Pattern 사용 예
- XML document parser
- 여러 가지 파싱 방법을 제공하기 위해 팩토리 메서드 패턴으로 구현됨

- Java.util.Calendar, java.text.Numberformat, Spring BeanFactory, etc.
팩토리 메서드 패턴의 장점
- 코드 유지보수 용이: 객체 생성을 캡슐화하여 나머지 코드와 분리함으로써 유지보수가 쉬움
- 재사용성 증가: 객체 생성이 인터페이스를 통해 이루어져 인터페이스 기반의 재사용이 용이 (Client 코드의 수정 최소화)
- 단일 책임 원칙(SPR) 준수: 팩토리 메서드 패턴은 객체의 생성 및 초기화에 대한 책임만 가짐
- 개방-폐쇄 원칙(OCP) 준수
- 기존 코드의 변경 없이 새로운 코드를 유연하게 추가할 수 있음
- 객체 생성을 위한 인터페이스를 정의하지만, 어떤 클래스를 인스턴스화 할지는 서브 클래스가 결정
팩토리 메서드 패턴의 단점
- 코드 구조 복잡도 증가
- 각 제품 구현체마다 팩토리 객체를 모두 구현해주어야 하기 때문에 클래스 수가 많아지며, 한눈에 이해하기 어려움
- 객체 생성 오버헤드
- 추가적인 클래스 객체가 생성되므로 객체 생성에 따른 오버헤드가 발생하여 성능에 영향을 미칠 수 있음
팩토리 메서드 패턴의 개선 버전
- Enum Factory Pattern
- 서브 팩토리 클래스를 일일이 구현하지 않고, 하나의 enum 팩토리로 구성하여 클래스 폭발 문제를 완화
- Dynamic factory pattern(동적 팩토리 패턴)
- 서브 클래스의 수가 늘어나는 것을 Reflection API를 이용하여 동적으로 처리하여 해결
Enum Factory Method Pattern
- Factory Method Pattern의 문제점을 해결하고자 개선된 Factory Method Pattern
- 제품의 객체의 수마다 팩토리 서브 클래스를 모두 구현해주어야 함
- 여러 팩토리 객체가 생성되어 자원이 낭비될 수 있음

- Enum으로 팩토리 메서드 패턴을 구성해 준다면, 일일이 서브 팩토리 클래스 구현 없이 하나의 enum Factory에서 구성할 수 있음
- 추가적인 팩토리 클래스를 만들지 않으며, 동시에 단일 팩토리(Singleton) 객체로 제품 객체를 생성할 수 있음


- 위처럼 추가적인 팩토리 클래스를 만들지 않아야 하면서 동시에 단일 팩토리로 제품 객체를 생성할 수 있어야 함
- Enum을 이용하여 싱글톤 객체 생성

- Enum을 이용하면 멀티톤(multiton)으로도 일반화 가능
- Multiton: 여러 개의 관리되는 인스턴스를 가지는 클래스


- Enum factory method pattern도 새 종류를 추가할 때는 enum 본문을 수정해야 해서 OCP를 완전히 만족하지는 못함
- 그래도 수동 구현(static finanl)한 고정 집합의 멀티톤보다 나은 점 (참고)
- 인스턴스 유일성 보장: JVM이 상수당 1개 인스턴스를 강하게 보장(실수 여지 적음)
- 직렬화 안전성: 별도 readResolve 없이도 역직렬화 시 동일 인스턴스 유지
- 리플랙션 내성: 리플랙션으로 추가 인스턴스 생성이 사실상 불가
- 스레드 안전 초기화: 클래스 로딩 타이밍에 안전하게 한 번만 생성
- 간결한 문법/가독성: 상수 선언과 상수별 동작 오버라이드가 짧고 명확
- 부가 기능: $values()$, $valueOf()$, $name()$ 사용 등 표준 유틸 제공
- 자체적 의의(vs 기존 Factory Method Pattern)
- 새로운 제품 추가: Product 클래스를 추가하고, EnumFactory에 팩토리 객체만 추가하면 됨
- 싱글톤 공유: 하나의 Factory 객체는 싱글톤으로 공유되어 외부에서 객체로 만들어 줄 필요가 없음
- 객체 생성에 따른 부담 완화
- 단점
- 새로운 Factory 추가 시 기존의 Enum 코드가 수정되어야 함
(코드 구조의 복잡성을 줄이지만 OCP를 못 지키는 trade-off 발생) - 만일 Factory가 복잡한 상속 계층으로 구성된다면, Enum은 클래스 상속이 안 되기 때문에 상속 구조 표현에 한계 존재
- ex) ↓
- 새로운 Factory 추가 시 기존의 Enum 코드가 수정되어야 함
// 제품 계층
abstract class Shape { }
class Circle extends Shape { /* radius 등 */ }
class Rectangle extends Shape { /* w, h 등 */ }
// 1단계: 공통 템플릿 공장
abstract class BaseShapeFactory {
public final Shape order(String spec) {
audit("order: " + spec);
validate(spec);
Shape s = createShape(spec); // 변하는 부분
postProcess(s);
return s;
}
protected void validate(String spec) { /* 공통 검증 */ }
protected void postProcess(Shape s) { /* 공통 후처리 */ }
protected void audit(String msg) { /* 로깅 */ }
protected abstract Shape createShape(String spec);
}
// 2단계: 보안 검증 추가
abstract class SecureShapeFactory extends BaseShapeFactory {
@Override protected void validate(String spec) {
super.validate(spec);
checkAuth();
}
protected void checkAuth() { /* 인증/권한 확인 */ }
}
// 3단계: 캐싱 추가
abstract class CachingShapeFactory extends SecureShapeFactory {
private final java.util.Map<String, Shape> cache = new java.util.HashMap<>();
@Override public final Shape order(String spec) {
if (cache.containsKey(spec)) return cache.get(spec);
Shape s = super.order(spec);
cache.put(spec, s);
return s;
}
}
// 최종 구체 공장들
class CircleFactory extends CachingShapeFactory {
@Override protected Shape createShape(String spec) {
// spec 파싱해서 Circle 생성
return new Circle();
}
}
class RectangleFactory extends CachingShapeFactory {
@Override protected Shape createShape(String spec) {
// spec 파싱해서 Rectangle 생성
return new Rectangle();
}
}
- enum은 다른 클래스를 상속할 수 없어, 위와 같은 복잡한 공통 절차를 다중 상속하며 단계적으로 재사용한 구조는 enum으로 바꿀 수 없음
- 클래스 상속을 유지하거나, 인터페이스 default/조합/위임으로 구조를 바꾸는 편이 안전함 (참고)
