Factory 패턴 개념
1. Factory 패턴이란?
Factory 패턴은 객체 생성 로직을 별도의 클래스나 메서드로 위임하여 클라이언트 코드와 객체 생성 과정을 분리하는 디자인 패턴입니다. 즉, 어떤 객체를 생성해야 할지를 클라이언트가 직접 알지 않아도 되며, 이로 인해 코드의 유연성과 유지보수성이 높아집니다.
사용 목적
- 객체 생성의 책임 분리
- 객체 종류에 따라 생성 로직 분기 가능
- 생성 과정을 캡슐화하여 클라이언트와 분리
적용 사례
- 다양한 구현체를 조건에 따라 반환해야 할 때
- 외부 설정 값이나 파라미터에 따라 객체를 동적으로 생성할 때
- Spring의 FactoryBean, BeanFactory, ApplicationContext.getBean(...)
대부분의 간단한 예제나 작은 규모의 서비스에서는 Factory 패턴을 생략하고 직접 객체를 생성하는 방식으로 구현되는 경우가 많습니다. 하지만 구현체가 늘어나거나, 조건에 따라 다른 객체가 필요해지는 순간부터는 Factory 패턴을 도입하는 것이 유지보수성과 확장성 측면에서 매우 효과적입니다.
2. Spring에서의 Factory 패턴
Spring은 DI 컨테이너 자체가 거대한 Factory입니다. ApplicationContext.getBean()을 통해 Bean을 생성 및 주입하며, 개발자가 직접 Factory를 구현할 수도 있습니다.
예시 1: 간단한 팩토리 메서드 구현
public class AnimalFactory {
public static Animal create(String type) {
return switch (type.toLowerCase()) {
case "dog" -> new Dog();
case "cat" -> new Cat();
default -> throw new IllegalArgumentException("Unknown type: " + type);
};
}
}
예시 2: 인터페이스 기반 전략과 함께 사용
public interface NotificationSender {
void send(String message);
}
public class EmailSender implements NotificationSender {
public void send(String message) {
System.out.println("Email: " + message);
}
}
public class SmsSender implements NotificationSender {
public void send(String message) {
System.out.println("SMS: " + message);
}
}
public class NotificationFactory {
public static NotificationSender getSender(String type) {
return switch (type.toLowerCase()) {
case "email" -> new EmailSender();
case "sms" -> new SmsSender();
default -> throw new IllegalArgumentException("Invalid type");
};
}
}
3. 장단점 정리
장점
- 객체 생성 로직과 사용 로직을 분리하여 책임 명확화
- 변경에 유연함 (Open-Closed Principle)
- 코드 재사용성과 테스트 용이성 향상
단점
- 클래스 수 증가
- 복잡도 증가 가능
- 간단한 케이스에서는 오히려 오버엔지니어링이 될 수 있음
4. 실무 적용 팁
1) 시스템 규모가 작을 때는 직접 객체를 생성하거나 고정된 방식으로 주입해도 큰 문제가 없지만, 규모가 커지고 모듈이 분리되거나 MSA 구조로 확장되면 Factory 패턴의 구조적 한계가 드러날 수 있습니다. 특히 ApplicationContext.getBean()을 사용하는 방식은 컴파일 타임 의존성이 명확하지 않아, 모듈 간 의존성이 꼬이거나 빌드 타이밍에 에러가 발생할 수 있습니다. MSA 환경에서는 객체 생성을 명확히 선언하고, 외부 시스템과의 연계를 고려해 전략 패턴, 인터페이스 기반의 DI, 또는 설정 기반의 유연한 Bean 등록 방식이 더 적합한 경우가 많습니다.
2) ApplicationContext.getBean(...)은 Spring의 Factory 기능 중 하나지만, 남발할 경우 의존성이 명확하지 않아 프로젝트 구조를 복잡하게 만들 수 있습니다. 특히 A 모듈에서 특정 Bean을 getBean()으로 가져다 쓰고, B 모듈이 이를 몰랐을 경우 런타임 에러나 순환 참조, 빌드 실패 등 다양한 문제로 이어질 수 있습니다. 따라서 가능한 경우 명시적인 생성자 주입(@Autowired)을 활용하고, 동적 조건에 따른 Bean 선택이 필요한 상황에서만 제한적으로 사용하는 것이 좋습니다.
- Service Layer에서 조건에 따라 여러 구현체를 리턴해야 할 때 매우 유용
- ApplicationContext를 주입받아 직접 Bean을 생성하거나 동적으로 주입할 수도 있음
@Component
public class SenderRouter {
private final Map<String, NotificationSender> senderMap;
public SenderRouter(List<NotificationSender> senders) {
this.senderMap = senders.stream().collect(Collectors.toMap(
sender -> sender.getClass().getSimpleName().toLowerCase().replace("sender", ""),
Function.identity()
));
}
public NotificationSender route(String type) {
return senderMap.getOrDefault(type, new EmailSender());
}
}
BeanFactory vs FactoryBean 간단 개념 비교
Spring에서 Factory 개념은 내부적으로도 다양하게 구현되어 있습니다. 그중 대표적인 것이 BeanFactory와 FactoryBean입니다.
BeanFactory
- Spring DI 컨테이너의 가장 기본적인 인터페이스
- ApplicationContext의 상위 개념이며, getBean() 메서드를 통해 Bean을 생성하고 관리
- 기본적으로 Lazy Loading 전략을 사용
- 실제로는 ApplicationContext가 거의 모든 경우에 대체함
BeanFactory factory = new XmlBeanFactory(new ClassPathResource("beans.xml"));
MyService service = factory.getBean("myService", MyService.class);
FactoryBean
- 개발자가 직접 구현할 수 있는 특별한 Bean 생성용 인터페이스
- getObject() 메서드가 반환하는 객체가 실제로 Spring Container에 등록됨
- 외부 API, Proxy, 복잡한 설정 기반 객체 생성 등에 활용
public class MyConnectionFactory implements FactoryBean<Connection> {
@Override
public Connection getObject() {
return DriverManager.getConnection("jdbc:mysql://...");
}
@Override
public Class<?> getObjectType() {
return Connection.class;
}
}
- 이 경우 getBean("myConnectionFactory")를 호출하면 Connection 객체가 반환됨
- 실제 FactoryBean 객체를 가져오고 싶다면 &myConnectionFactory를 사용함
요약 비교표
항목BeanFactoryFactoryBean
항목 | BeanFactory | FactoryBean |
역할 | Spring의 핵심 DI 컨테이너 | 커스텀 객체를 생성하는 Bean 인터페이스 |
사용 위치 | Spring 내부/초기 구현 | 개발자 커스터마이징 시 주로 사용 |
특징 | Lazy 로딩, 범용 컨테이너 역할 | getObject()의 결과가 Bean으로 등록됨 |
항목 | 내용 |
패턴 목적 | 객체 생성 책임 분리 및 유연한 인스턴스 선택 |
주요 키워드 | 생성 캡슐화, 조건 분기, 구현체 선택 |
Spring 연관 | BeanFactory, FactoryBean, 전략 패턴 결합 |