본문 바로가기
Concepts/Design Pattern

객체 생성 패턴- 추상 팩토리 패턴

by ocwokocw 2021. 2. 15.

- 참조: GoF의 디자인 패턴

- 참조: https://www.baeldung.com/java-abstract-factory-pattern

- 추상 팩토리

추상 팩토리는 상세화된 서브 클래스를 정의하지 않고, 관련성이 있거나 독립적인 여러 객체 군을 생성하기 위한 인터페이스를 제공한다. 문장이 어려워 이해하지 못했더라도 낙담할 필요는 없다. 다음에 나올 내용들을 살펴본 뒤 이 문장을 이해하면 된다.

 

모티프, 프리젠테이션 매니저와 같은 사용자 인터페이스 툴킷 프로그램은 서로 다른 룩앤필을 가지고 있다. 만약 이 툴킷을 이용하여 응용프로그램을 개발한다고 가정해보자. 프로젝트 초반에 모티프 툴킷을 이용하여 개발하자고 표준을 잡고 개발을 시작한다. 개발자들은 열심히 모티프 툴킷을 이용하여 개발을 완료하였다. 응용프로그램 여기저기에 모티프 툴킷을 이용한다. 프로젝트 진행도중 갑자기 프리젠테이션 매니저 툴킷에 대한 이식성도 고려해달라고 한다.

 

위의 문제를 해결하기 위해 어떻게 해야 할까? 아주 단순하게 생각한다면 소스를 복사해서 모티프 툴킷에 의존하는 코드를 프리젠테이션 매니저 툴킷으로 변경하거나, 추후 제3, 제4 툴킷을 지원하기 위해 뒤늦게 라도 공통화할 방법을 생각할것이다.

 

이런 문제의 경우 구체적인 툴킷에 의존하게 하지 말고 추상 클래스를 정의하여 해결하는것이 좋다.

위의 다이어그램은 위에서 언급한 시나리오를 추상 팩토리 패턴으로 설계하여 UML로 나타낸것이다. 왜 이름이 추상 팩토리일까? 이는 객체 생성과정을 공장에서 제품을 생산하는 모습에 빗대었기 때문이다. 이 패턴에 등장하는 참여자에는 추상 팩토리(AbstractFactory), 구체 팩토리(ConcreteFactory), 추상 제품(AbstractFactory), 구체 제품(ConcreteFactory), 사용자(Client)가 있다.

 

사실 아주 단순하게 생각하면 구체 팩토리(PMWidgetFactory, MotifWidgetFactory)에서 생산한 구체 제품(PMScrollBar, MotifScrollBar, PMWindow, MotifWindow)만 있으면 그만이다. 근데 왜 추상이라는 개념이 필요할까?

추상 팩토리에 대해 생각해보자. 신규 팩토리(NewFactory)를 추가하려고 할 때, WidgetFactory가 있으면 NewFactory 에서 우리가 어떤 제품을 필요로 하는지 알 수 있게 된다. 또한 우리가 새로운 제품(NewProduct)이 필요할 때, 구체 팩토리들에게 일괄적으로 우리가 NewProduct가 필요하다고 얘기할 수 있게 된다.

 

추상 제품은 왜 필요할까? 구체 팩토리(PMWidgetFactory, MotifWidgetFactory)에게 ScrollBar를 만들어 달라고 얘기하면 각 팩토리에서 만든 ScrollBar 제품은 다를 수 밖에 없다. 우리가 ScrollBar 제품이 필요한것은 scroll 기능을 원하기 때문이다. 즉 추상제품이라는 개념은 각 팩토리에게 우리가 필요한 ScrollBar 제품에는 scroll 기능이 들어가있어야 한다고 얘기하는 것과 같다.

 

간혹 추상 팩토리라는 이름 때문인지 추상 팩토리나 추상 제품을 꼭 abstract 키워드를 이용해서 구현해야한다고 생각하기도 하는데, 이는 추상 팩토리의 추상이라는 단어를 사전적인 의미로 사용한것과 소스코드의 추상(abstract) 키워드를 혼동한것이라고 할 수 있다. 추상이라는것은 공통되는 특성을 추출한것이므로 인터페이스로 코드를 구현하여도 상관없다.


- Java 구현 예제

위의 시나리오를 Java로 구현해보자. 이 글을 읽는 독자라면 분명히 아래 소스코드보다 더 잘할 수 있으니 각자 구현해보자. 

 

우선 추상 제품에 원하는 기능을 정의한다.

 

public interface ScrollBar {

	void scroll();
}

public interface Window {

	void close();
}

 

추상 제품을 구현한 구체 제품들의 기능을 구현한다.

 

public class MotifScrollBar implements ScrollBar {

	@Override
	public void scroll() {
		System.out.println("MOTIF scroll method");
	}
}

public class MotifWindow implements Window{

	@Override
	public void close() {
		System.out.println("Motif close method");
	}
}

public class PMScrollBar implements ScrollBar {

	@Override
	public void scroll() {
		System.out.println("PM scroll method");
	}
}

public class PMWindow implements Window{

	@Override
	public void close() {
		System.out.println("PM window close method");
	}
}

 

추상 팩토리에서 필요한 제품을 생산하는 메소드를 정의한다.

 

public interface WidgetFactory {

	ScrollBar createScrollBar();
	
	Window createWindow();
}

 

추상 팩토리를 구현하여 제품을 생산하는 구체 팩토리를 정의한다. 

 

public class MotifWidgetFactory implements WidgetFactory{

	@Override
	public ScrollBar createScrollBar() {
		return new MotifScrollBar();
	}

	@Override
	public Window createWindow() {
		return new MotifWindow();
	}
	
}

public class PMWidgetFactory implements WidgetFactory {

	@Override
	public ScrollBar createScrollBar() {
		return new PMScrollBar();
	}

	@Override
	public Window createWindow() {
		return new PMWindow();
	}

}

 

이후 인자에 따라 어떤 팩토리를 제공할것인지 팩토리 Provider 를 구현한다.

 

public enum Factory {
	MOTIF, PM;
}

public class FactoryProvier {

	public static WidgetFactory createWidgetFactory(Factory customer) {
		
		if(Factory.MOTIF.equals(customer)) {
			return new MotifWidgetFactory();
		}
		else if(Factory.PM.equals(customer)) {
			return new PMWidgetFactory();
		}
		else {
			throw new RuntimeException("UnSupportedCustomerType");
		}
	}
	
}

 

각 팩토리에서 해당 제품을 적절하게 생산하는지 테스트해본다.

 

public static void main(String[] args){
	
	WidgetFactory motifWidgetFactory = FactoryProvier.createWidgetFactory(Factory.MOTIF);
	
	ScrollBar motifScroll = motifWidgetFactory.createScrollBar();
	motifScroll.scroll();
	
	Window motifWindow = motifWidgetFactory.createWindow();
	motifWindow.close();
	
	WidgetFactory pmWidgetFactory = FactoryProvier.createWidgetFactory(Factory.PM);
	
	ScrollBar pmScroll = pmWidgetFactory.createScrollBar();
	pmScroll.scroll();
	
	Window pmWindow = pmWidgetFactory.createWindow();
	pmWindow.close();
	
}

- 장점과 단점

일단 사용하는 제품군의 일관성을 높여주는것이 장점이다. 위의 시나리오에서 MotifScrollBar는 반드시 MotifWIndow와 같이 사용해야 할 때, 소스코드 여기저기에 제품 객체를 생성하는 코드가 여기저기 나타나면 제어하기가 쉽지 않다. 하지만 추상 팩토리 패턴은 이런 일관성을 보장할 수 있다.

 

또한 사용하는 제품군을 일괄로 변경할 수 있다. 만약 Motif 제품군을 사용하다가 일괄적으로 PM 제품군으로 변경해야 한다면 생성하는 팩토리를 PM 구체 팩토리로 변경하면 된다.

 

이런 장점이 곧 약점이 되기도 한다. 만약 새로운 종류의 제품이 필요하여 추상 팩토리에 해당 제품을 생산하는 연산을 추가하면, 해당 구체 팩토리 코드가 모두 변경된다. 제품변경또한 마찬가지로 구체 팩토리들을 모두 변경해야 한다.

댓글