팩토리 패턴
공장에서 미리 정해둔 여러 제품들을 만들어 내듯이 Factory를 통해 생성 가능한 여러 객체들을 생성하는 객체지향 디자인 패턴이다.
팩토리 패턴은 아래 나오는 단순 팩토리 패턴을 지칭하기도 하고, 확장된 형태의 패턴들까지 통틀어 지칭하기도 한다.
싱글톤 패턴(Singleton Pattern)과 마찬가지로 객체 생성에 대한 디자인 패턴(생성 패턴; Creational Pattern)에 해당한다.
단순 팩토리 패턴
다양한 구현체(Product)가 있고, 그 중에서 특정 구현체를 생성할 때
생성에 대한 책임을 분리하여 특정 클래스에 위임하는 것이 팩토리 패턴(Factory Pattern)이다.
뒤에서 설명할 확장된 팩토리 패턴들과 구분하여 단순 팩토리 패턴(Simple Factory Pattern)이라고도 한다.

구조가 매우 간단하여 패턴이라고 보기에도 어렵고, 이 자체만으로는 4인방(Gang of Four) 패턴에 해당하지도 않는다.
하나의 메서드를 통해 동일한 클래스의 속성 값을 다르게 생성하기도 하지만 동일한 타입의 여러 객체들을 생성하기도 하는데
이런 경우 클래스의 Upcasting 기능을 이용해 다음과 같이 동일한 부모 클래스나 인터페이스를 상속받는 구현체들을 생성할 수 있다.

참고로 추상클래스와 다르게 interface는 추상 method 선언만 가능하고 기본 구현체를 만들 수 없는데, Java의 경우 8버전 이상부터는 interface에서도 default method를 구현할 수 있고, 9버전 이상에서는 private method도 구현이 가능하여 추상클래스의 기능도 대부분 대체가 가능하다. 따라서 대부분의 경우에는 interface만으로 구현 가능하고, 추상클래스가 꼭 필요한 경우는 내부적으로 변수를 관리할 필요가 있는 경우 정도이다.
Java 예시 코드
추상 클래스 Product를 상속받는 ProductA, B, C를 선언하고, Factory에서 입력받은 type에 따라 알맞은 Product를 생성한다.
public abstract class Product {
private String name;
public String getName() { return name; }
protected void setName(String name) { this.name = name; }
}
public class ProductA extends Product {
public ProductA() {
setName("ProductA");
}
}
public class ProductB extends Product {
public ProductB() {
setName("ProductB");
}
}
public class ProductC extends Product {
public ProductC() {
setName("ProductC");
}
}
public class Factory {
public static Product createProduct(String type) {
switch (type) {
case "A" -> {
return new ProductA();
}
case "B" -> {
return new ProductB();
}
case "C" -> {
return new ProductC();
}
}
throw new IllegalArgumentException("Wrong type");
}
}
다음과 같이 Client는 객체 생성 책임을 Factory에게 맡기고, 상황에 맞게 호출만 해주면 된다.
public class Client {
public static void main(String[] args) {
Product a = Factory.createProduct("A");
System.out.println("a: " + a.getName());
Product b = Factory.createProduct("B");
System.out.println("b: " + b.getName());
Product c = Factory.createProduct("C");
System.out.println("c: " + c.getName());
}
}
실행 결과
a: ProductA
b: ProductB
c: ProductC
장점
객체 생성에 대한 책임을 분리하여 Factory에 위임했기 때문에, 이를 호출하는 클라이언트는 객체 생성 책임에서 자유로워진다.
클라이언트는 객체 생성에 대한 세부로직을 신경쓸 필요가 없고, 변경사항이 생기더라도 Factory만 수정해주면 된다.
단점
새로운 제품이 추가되거나 기존 제품 중 하나만 변경되더라도 팩토리의 객체 생성 메서드를 매번 수정해야 한다.
예를 들면 ProductD가 새로 추가된다거나 ProductC의 생성로직이 변경될 경우 Factory의 createProduct() 함수를 매번 수정해야 한다.
코드 수정은 언제나 버그의 위험성을 안고 있고, 따라서 다른 제품들은 변경사항이 없더라도 영향을 받을 잠재적인 위험이 있다.
변경이 필요한 제품만 코드를 수정하고, 나머지 제품들은 코드 수정에 영향을 받지 않도록 하려면 어떻게 해야할까?
이 문제를 해결하기 위한 방법이 팩토리 메서드 패턴이다.
팩토리 메서드 패턴
단순 팩토리 패턴이 객체 생성에 대한 책임을 단순히 분리된 클래스로 위임하는 것이라면
팩토리 메서드 패턴(Factory Method Pattern)은 단순 팩토리 패턴에서 객체 생성 메서드를 추상화하여 정의와 구현을 분리하고
실제 객체 생성에 대한 구현을 서브 클래스로 위임하는 패턴이다.
즉, 단순 팩토리 패턴에 템플릿 메서드 패턴(Template Method Pattern)이 결합된 형태라고 볼 수 있다.
팩토리 메서드 패턴은 객체를 생성하는 인터페이스(Creator)를 정의하고,
구체적으로 어떤 인스턴스를 만들지는 서브 클래스(ConcreteCreator)에서 결정하는 패턴이다.

단순 팩토리 패턴에서는 하나의 팩토리에서 하나의 메서드로 다양한 객체 생성을 모두 처리했다면
팩토리 메서드 패턴에서는 특정 객체를 생성하는 다양한 팩토리(ConcreteCreator)를 제공한다.
즉, 각각의 팩토리는 정해진 구현체를 생성하며, 필요에 따라 이러한 팩토리를 여러개 만들어 사용한다.
Java 예시 코드
Factory를 추상화하여 구현한다.
public interface Factory {
}
public class AFactory implements Factory {
}
장점
Creator와 Product를 모두 추상화 함으로써 추상화의 여러가지 장점을 얻을 수 있다.
팩토리 인터페이스에서는 Product의 구체 클래스를 신경쓰지 않아도 되므로 실제 로직을 구현하기 전에 기능의 전체적인 틀을 설계할 때 용이하다. 그리고 하위 클래스에 다형성을 부여하며 다양한 요구사항에 좀 더 유연하게 대응이 가능하다.
그리고 무엇보다 추상화를 통해 Creator와 Product의 결합도를 낮춤(Loosly Coupled)으로써 확장에 열려있고, 변경에는 닫혀있는 구조가 가능해진다. 이를 OCP(Open-Closed Principle)이라고 한다.
OCP(Open-Closed Principle) 개방-폐쇄 원칙
확장에 열려있고 변경에 닫혀있도록 설계하는 원칙.
기존의 코드를 변경하지 않으면서 기능을 추가할 수 있도록 설계하는 원칙.
객체지향설계의 SOLID 원칙 중 O에 해당한다.
즉, 객체 생성 로직이 추가되거나 변경될 때 나머지 기존 로직의 변경을 최소화하거나 혹은 변경 없이 구현할 수 있다.
개별 로직의 코드가 간결해지고, 기존 코드들이 변경의 영향을 받지 않기 때문에 안전하다.
잦은 변경이 예상되는 경우 팩토리 메서드 패턴을 적용하도록 리팩토링을 하면 유지보수가 용이해진다.
이 때 각 Creator들은 하나의 정해진 객체 생성 기능을 수행하고, 자연스럽게 SRP(Single Responsibility Principle)도 준수하게 된다.
SRP(Single Responsibility Principle) 단일 책임 원칙
하나의 클래스는 하나의 책임만 가지며, 변경의 이유가 단 하나가 되도록 설계하는 원칙.
객체지향설계의 SOLID 원칙 중 S에 해당한다.
단점
역할을 나누다보니 클래스들이 늘어나면서 구조가 복잡해 질 수 있다.
구조가 복잡해지는 것은 별거 아닐 수 있지만 다른 사람이 유지보수 할 때에는 비용이 더 소모된다.
기능이 간단하거나 변경할 가능성이 거의 없는 경우에는 굳이 패턴을 적용하여 구조를 복잡하게 만들기 보다는 한 눈에 알아보기 쉽도록 간단한 구조로 작성하는 것이 더 유리하다. 무조건 패턴을 적용하기보다 필요한 경우 상황에 맞게 적용한다.
다른 패턴들도 마찬가지지만 패턴을 적용하기 위한 코드를 만들지 말고, 상황에 맞는 유용한 패턴을 적절하게 잘 사용하는 것이 중요하다.
'Programming > Design' 카테고리의 다른 글
| Spring Boot 프로젝트 구조 설계 가이드 (MVC, Layered, Domain 기반, Hexagonal) (0) | 2025.10.07 |
|---|