티스토리 뷰
1. 팩토리 메서드(Factory Method) 패턴이란?
팩토리 메서드(Factory Method) 패턴이란 인스턴스를 생성하는 공장을 템플릿 메서드(Template Method) 패턴으로 구성한 디자인 패턴이다. 팩토리 메서드 패턴에서는 인스턴스 생성 방법을 상위 클래스에서 결정하되, 생성의 구체적인 내용은 하위 클래스에서 담당한다.
즉, Factory 인터페이스에서 객체를 생성하는 템플릿을 미리 만들어놓고, 서브 Factory 클래스에서 각자의 형태에 맞게 템플릿의 상세 내용을 구현하는 것이다.
2. 예제 코드로 이해하기
2-1. 팩토리 메서드 패턴은 패키지를 분리할 수 있다.
- framework : 추상화된 클래스를 담는 패키지
- idcard : 구체화된 클래스를 담는 패키지
추상화와 구현부를 패키지로 분리하여 각 부분의 책임을 명확하게 구분할 수 있다. 이는 객체 생성 로직이 수정되거나 새로운 제품이 추가될 때 해당 패키지 내에서만 수정이 이루어지므로, 코드의 변경 영향 범위를 최소화할 수 있다.
2-2. 추상화 패키지(framework)
- Product 클래스
Product 클래스는 생성되는 제품을 의미한다.
package factory_method.framework;
// Product : 제품 구현체를 추상화
public abstract class Product {
// 구체적인 내용은 하위 클래스인 IDCard 클래스에서 작성
public abstract void use();
}
- Factory 클래스
템플릿 메서드(Template Method) 패턴을 사용하여 객체 생성을 추상화한다. 제품을 생성하고(create) 등록하는(register) 메서드는 서브 Factory 클래스에서 오버라이딩한다. 서로 다른 제품을 만드는 서브 Factory 클래스에서 세부 내용을 구현한다.
접근 제어자로 protected를 사용하는 이유는 객체의 생성 및 등록 과정의 세부 구현을 서브 Factory 클래스에서 작성하도록 위임하기 위해서이다. 서브 Factory 클래스 이외에는 두 메서드를 직접 호출할 필요가 없기 때문에 관련 없는 곳에서 호출하는 것을 방지한다.
package factory_method.framework;
// Creator : 최상위 공장 클래스
// 팩토리 메서드를 추상화하여 서브 공장 클래스에서 구현한다.
public abstract class Factory {
// 템플릿 메서드(Template Method) 패턴
// 객체 생성을 템플릿화 시켰기 때문에, final을 사용하여 오버라이딩을 방지한다.
public final Product create(String owner) {
Product product = createProduct(owner); // 서브 공장 클래스에서 구체화한 팩토리 메서드
registerProduct(product); // 서브 공장 클래스에서 구체화한 메서드
return product; // 생성된 객체 반환
}
// 접근 제어자로 protected를 사용하여 외부에 노출을 방지한다.
protected abstract Product createProduct(String owner); // 팩토리 메서드
protected abstract void registerProduct(Product product); // 서브 공장 클래스에서 재정의
}
2-3. 구현부 패키지(idcard)
- IDCardA
package factory_method.idcard;
import factory_method.framework.Product;
// 제품(Product) 구현체
public class IDCardA extends Product {
private String owner;
public IDCardA(String owner) {
System.out.println(owner + "의 카드를 만듭니다.");
this.owner = owner;
}
@Override
public void use() { // 상위 클래스의 추상 메서드를 구현한다.
System.out.println(this + "을 사용합니다.");
}
@Override
public String toString() {
return "[IDCard:" + owner + "]";
}
public String getOwner() {
return owner;
}
}
- IDCardB
package factory_method.idcard;
import factory_method.framework.Product;
// 제품(Product) 구현체
public class IDCardB extends Product {
private String owner;
public IDCardB(String owner) {
System.out.println(owner + "의 카드를 만듭니다.");
this.owner = owner;
}
@Override
public void use() { // 상위 클래스의 추상 메서드를 구현한다.
System.out.println(this + "을 사용합니다.");
}
@Override
public String toString() {
return "[IDCard:" + owner + "]";
}
public String getOwner() {
return owner;
}
}
- IDCardAFactory
서브 Factory 클래스에서 IDCardA 인스턴스를 생성하여 Factory 클래스에서 추상화된 createProduct(), registerProduct()의 세부 내용을 구현한다.
package factory_method.idcard;
import factory_method.framework.Factory;
import factory_method.framework.Product;
// ConcreteCreator : 서브 공장 클래스
// 각 서브 공장 클래스에 맞는 제품을 생성하도록, 최상위 클래스(Factory)의 추상 메서드를 재정의한다.
public class IDCardAFactory extends Factory {
@Override
protected Product createProduct(String owner) { // 최상위 공장 클래스(Factory)의 추상 메서드 재정의
return new IDCardA(owner);
}
@Override
protected void registerProduct(Product product) { // 최상위 공장 클래스(Factory)의 추상 메서드 재정의
System.out.println(product + "을 등록했습니다.");
}
}
- IDCardBFactory
package factory_method.idcard;
import factory_method.framework.Factory;
import factory_method.framework.Product;
// ConcreteCreator : 서브 공장 클래스
// 각 서브 공장 클래스에 맞는 제품을 생성하도록, 최상위 클래스(Factory)의 추상 메서드를 재정의한다.
public class IDCardBFactory extends Factory {
@Override
protected Product createProduct(String owner) { // 최상위 공장 클래스(Factory)의 추상 메서드 재정의
return new IDCardB(owner);
}
@Override
protected void registerProduct(Product product) { // 최상위 공장 클래스(Factory)의 추상 메서드 재정의
System.out.println(product + "을 등록했습니다.");
}
}
2-4. Main 클래스
객체 생성을 템플릿화 시켜 추상화한 Factory 클래스와, 각 제품(IDCardA, IDCardB)를 생성하는 서브 Factory 클래스(IDCardAFactory, IDCardBFactory)를 사용한다.
public class Main {
public static void main(String[] args) {
Factory factory1 = new IDCardAFactory();
Factory factory2 = new IDCardBFactory();
Product card1 = factory1.create("Youngjin Kim");
Product card2 = factory1.create("Heungmin Son");
Product card3 = factory2.create("Kane");
card1.use();
card2.use();
card3.use();
}
}
3. 팩토리 메서드(Factory Method) 패턴의 장단점
3-1. 장점
- 단일 책임 원칙 준수
- 추상화와 구현부 패키지를 분리하여 코드 유지 보수가 쉽다.
- 클래스는 하나의 책임(역할)만 갖는다.
- 객체 생성 책임을 Factory 클래스로 분리하여, 클라이언트 코드에서 직접 객체를 생성하지 않는다.
- 개방 폐쇄 원칙 준수
- 기존의 코드를 수정하지 않고, 새로운 제품을 생성할 수 있다.
- 확장엔 열려있고, 기존의 코드 수정에는 닫혀있다.
- 새로운 기능 추가 시에 기존의 코드를 수정할 필요 없이, 새로운 코드로 기능을 확장할 수 있다.
- Factory, Product 클래스의 코드 수정 없이, 새로운 서브 Factory 클래스를 생성하여 구체적인 로직을 작성할 수 있다.
3-2. 단점
- 클래스 개수가 많아짐에 따라 복잡성 증가
- 추상화된 클래스와 이를 구현하는 클래스가 생기기 때문에, 클래스의 개수가 자연스럽게 많아진다.
- 추상화된 클래스는 객체 생성의 구체적인 내용을 포함하지 않기 때문에, 코드의 흐름을 파악하기 위해서는 구체 클래스를 반드시 함께 확인해야 한다. 이는 코드의 가시성을 떨어뜨리기 때문에, 전체적인 구현 내용을 파악하는데 시간이 더 소요될 수 있다.
4. 결론
팩토리 메서드 패턴뿐만 아니라 다른 디자인 패턴을 사용할 때, 어떠한 패턴을 사용하였는지 주석으로 남길 필요가 있다. 내가 작성한 코드를 다른 개발자가 보았을 때, 내 의도와는 다르게 해석할 가능성이 있기 때문이다. 따라서 프로그램 주석 또는 개발 문서 안에 사용된 디자인 페턴의 명칭과 의도를 작성하는 것이 좋은 방법이다.
참고
https://product.kyobobook.co.kr/detail/S000200311846
https://inpa.tistory.com/entry/GOF-💠-팩토리-메서드Factory-Method-패턴-제대로-배워보자