[디자인 패턴] (GoF 생성패턴) Abstract Factory Pattern에 대하여
안녕하세요! 디자인 패턴 연재 시리즈, 이번 시간에는 생성 패턴(Creational Patterns)의 한 종류인 추상 팩토리(Abstract Factory) 패턴에 대해 알아보겠습니다.
정의
추상 팩토리 패턴은 구체적인 클래스를 지정하지 않고도, 서로 관련성이 있거나 의존적인 객체들의 집합을 생성할 수 있는 인터페이스를 제공하는 패턴입니다. 비유로 설명을 해보겠습니다 예를 들어 가구 공장이 있다고 상상해봅시다. 이 공장은 '모던 스타일'과 '앤티크 스타일' 두 가지 테마의 가구를 생산할 수 있습니다.
- 모던 가구 공장(ConcreteFactory)에 가서 가구를 주문하면 '모던 의자'와 '모던 테이블'을 받게 됩니다.
- 앤티크 가구 공장(ConcreteFactory)에 주문하면 '앤티크 의자'와 '앤티크 테이블'을 받게 되죠.
여기서 중요한 점은, 클라이언트(가구를 주문하는 사람)는 어떤 스타일의 가구를 원할지(모던 or 앤티크)만 결정하면, 그에 맞는 한 세트의 가구들이 알아서 생성된다는 것입니다. 클라이언트는 의자나 테이블이 어떻게 만들어지는지에 대한 구체적인 과정을 알 필요가 없습니다. 이처럼 '관련된 객체들의 묶음'을 일관성 있게 생성하는 것이 바로 추상 팩토리 패턴의 핵심입니다.
추상 팩토리 패턴은 다음과 같은 요소들로 구성됩니다.
- AbstractFactory (추상 팩토리)
- 관련된 제품 객체들을 생성하기 위한 인터페이스를 정의합니다. (예: createProductA(), createProductB())
- ConcreteFactory (구체적인 팩토리)
- AbstractFactory 인터페이스를 구현하며, 구체적인 제품 객체들을 생성합니다. (예: ConcreteFactory1은 ProductA1과 ProductB1을 생성)
- AbstractProduct (추상 제품)
- 제품 객체의 인터페이스를 정의합니다. (예: AbstractProductA, AbstractProductB)
- ConcreteProduct (구체적인 제품)
- ConcreteFactory에 의해 생성될 구체적인 제품 객체입니다. AbstractProduct 인터페이스를 구현합니다. (예: ProductA1, ProductB1)
- Client (클라이언트)
- AbstractFactory와 AbstractProduct 인터페이스에만 의존합니다. 구체적인 클래스 이름 없이 제품군을 생성하고 사용합니다.
사용예시
'모던 스타일'과 '앤티크 스타일'의 가구(의자, 테이블)를 생성하는 가구 공장 예제를 코드로 살펴보겠습니다.
// 제품군 중 하나인 의자에 대한 추상 제품
interface Chair {
void sitOn();
}
// 제품군 중 다른 하나인 테이블에 대한 추상 제품
interface Table {
void putOn();
}
위 코드는 생성할 가구들에 대한 공통된 인터페이스를 우선 정의합니다.
// 모던 스타일의 의자
class ModernChair implements Chair {
@Override
public void sitOn() {
System.out.println("모던한 의자에 앉습니다.");
}
}
// 모던 스타일의 테이블
class ModernTable implements Table {
@Override
public void putOn() {
System.out.println("모던한 테이블에 물건을 올려놓습니다.");
}
}
// 앤티크 스타일의 의자
class AntiqueChair implements Chair {
@Override
public void sitOn() {
System.out.println("앤티크한 의자에 앉습니다.");
}
}
// 앤티크 스타일의 테이블
class AntiqueTable implements Table {
@Override
public void putOn() {
System.out.println("앤티크한 테이블에 물건을 올려놓습니다.");
}
}
각 스타일에 맞는 구체적인 가구들을 구현합니다.
// 모던 스타일 가구를 생성하는 팩토리
class ModernFurnitureFactory implements FurnitureFactory {
@Override
public Chair createChair() {
return new ModernChair();
}
@Override
public Table createTable() {
return new ModernTable();
}
}
// 앤티크 스타일 가구를 생성하는 팩토리
class AntiqueFurnitureFactory implements FurnitureFactory {
@Override
public Chair createChair() {
return new AntiqueChair();
}
@Override
public Table createTable() {
return new AntiqueTable();
}
}
각 스타일에 맞는 가구들을 생성하는 구체적인 팩토리를 구현합니다.
위처럼 팩토리를 정의해두면 client 단에서는 다음과 같은 방식으로 Factory Method를 호출할 수 있습니다
class FurnitureStore {
private Chair chair;
private Table table;
// 클라이언트는 추상 팩토리 인터페이스에만 의존한다.
public FurnitureStore(FurnitureFactory factory) {
chair = factory.createChair();
table = factory.createTable();
}
public void arrangeFurniture() {
System.out.println("새로운 가구를 배치합니다.");
chair.sitOn();
table.putOn();
}
}
public class Client {
public static void main(String[] args) {
// 고객이 원하는 가구 스타일을 결정 (예: "Modern")
String style = "Modern"; // 또는 "Antique"
FurnitureFactory factory;
// 스타일에 따라 적절한 팩토리를 선택
if ("Modern".equalsIgnoreCase(style)) {
factory = new ModernFurnitureFactory();
} else if ("Antique".equalsIgnoreCase(style)) {
factory = new AntiqueFurnitureFactory();
} else {
throw new IllegalArgumentException("지원하지 않는 스타일입니다.");
}
// 선택된 팩토리를 사용하여 가구점을 구성
FurnitureStore store = new FurnitureStore(factory);
store.arrangeFurniture();
// 실행 결과 (style = "Modern"일 경우):
// 새로운 가구를 배치합니다.
// 모던한 의자에 앉습니다.
// 모던한 테이블에 물건을 올려놓습니다.
}
}
클라이언트는 ModernFurnitureFactory나 AntiqueFurnitureFactory 같은 구체적인 클래스를 직접 다루지 않고, FurnitureFactory라는 인터페이스를 통해 객체를 생성합니다. 덕분에 클라이언트 코드의 변경 없이도 가구 스타일을 쉽게 교체할 수 있습니다.
결론
그럼 지난 Factory Method에 비교해보면 생성객체를 묶어주는(군체) 인터페이스를 만들어준다는 큰 차이가 있는데, 이걸 언제 사용할까요?
- 객체가 생성되는 방식에 구애받지 않고, 관련된 객체들로 이루어진 '제품군'을 만들어야 할 때
- 예제처럼 가구 스타일(모던, 앤티크)에 따라 일관된 디자인의 가구(의자, 테이블)들을 생성해야 할 때 유용합니다.
- 여러 제품군 중 하나를 선택해서 시스템을 설정해야 하고, 한번 설정한 후에는 다른 제품군으로 쉽게 교체할 수 있어야 할 때
- 클라이언트 코드를 변경하지 않고 팩토리만 교체하면 전체 제품군이 바뀌므로 유연성이 높아집니다.
- 구체적인 클래스는 숨기고 인터페이스만 노출하여 시스템의 결합도를 낮추고 싶을 때
- 클라이언트는 구체적인 제품 클래스를 알 필요가 없으므로 시스템의 확장성과 유지보수성이 향상됩니다.
결론적으로는 Factory 구체 class는 숨기면서 제품의 Style별 여러 객체를 한번에 생성하고 싶을때 라고 정의할 수 있겠습니다.
기술사 공부할때는 Abstract Factory / Concreate Factory, Class Group 반환 등의 keyword를 사용하면 되겠네요. 추가로 Factory Method와의 비교도 같이 있으면 좋을거 같습니다. 팩토리 메서드는 하나의 제품을 만드는 방법을 서브클래스에게 위임하는 것이고, 추상 팩토리는 관련된 여러 제품들로 이루어진 세트를 만드는 공장을 제공하는 것입니다. 실제로 추상 팩토리 패턴의 내부는 종종 팩토리 메서드 패턴으로 구현되기도 합니다.
이번 시간에는 추상 팩토리 패턴에 대해 알아보았습니다. 관련된 객체들을 일관성 있게 생성해야 하는 복잡한 시스템에서 매우 유용하게 사용될 수 있는 강력한 패턴입니다. 다음 시간는 현대 프로그래밍의 객체 생성방식의 De-Facto처럼 사용되는 Singleton Pattern을 알아보겠습니다