CS/디자인패턴

[생성 패턴] Abstract Factory Method

_OB1N 2021. 5. 19. 22:29

추상 팩토리 메소드.

 

이름부터 왠지 패토리 메소드와 비슷한 기능을 수행할 것만 같다. 추상 팩토리 메소드 패턴을 정리하자면 아래와 같다.

 

팩토리를 생성하는 팩토리

Structure

 

 

 

설명

팩토리 메소드 패턴의 설명에서 사용했던 예제인 Parser를 다시 한번 생각해보자.

Factory Method Pattern을 사용한 Parser예제

DisplayService가 하나의 Parser 인터페이스를 갖는 다양한 Concrete 클래스(ResponseParser, ErrorParser, FeedbackParser)를 팩토리 메소드를 통해 생성한 예제였다. 

 

이 상황에서 한 발짝 더 나아가보자. ResponseParser가 A_ResponseParser, B_ResponseParser로 나누어진다면, ResponseDisplayService는 어떻게 될까. 다음과 같은 모습이 될 것이다.

public class ResponseDisplayService extends DisplayService {
	@Override
	public Parser getParser(String type) {
		switch (type) {
			case 'A': return new A_ResponseParser();
			case 'B': return new B_ResponseParser(); 
		}
		return null;
	}
}

ErrorParser와 FeedbackParser도 A와 B로 구분되면, ErrorDisplayService와 FeedbackDisplayService 또한 아래와 같이 수정되어야 한다.

public class ErrorDisplayService extends DisplayService {
	@Override
	public Parser getParser(String type) {
		switch (type) {
			case 'A': return new A_ErrorParser();
			case 'B': return new B_ErrorParser(); 
		}
		return null;
	}
}
public class FeedbackDisplayService extends DisplayService {
	@Override
	public Parser getParser(String type) {
		switch (type) {
			case 'A': return new A_FeedbackParser();
			case 'B': return new B_FeedbackParser(); 
		}
		return null;
	}
}

이렇게 코드가 변경되면서, 이전에는 DisplayService의 구상 클래스들이 하나의 오브젝트에 대한 생성 책임만 갖고 있었는데, 이제는 Parser 종류(A, B)만큼 오브젝트 생성의 책임이 늘어났다.

 

이러한 책임에서 벗어나게 하기 위해, 기존 Creator == Client 였던 구조에서, Creator != Client 인 구조로의 변경을 생각해볼 수 있다. 즉, getParser()를 별도의 클래스(Factory 클래스)로 뽑아낸다고 생각하면 된다. 이제 각 Parser들을 제조사(A, B)별로 모아 제공하는 팩토리를 아래와 같이 만들 수 있다.

public interface AbstractParserFactory{
	public Parser getParserInstace(String type);
}
public class A_ParserFactory implements AbstractParserFactory{
	@Override
	public Parser getParserInstance(string type){
		switch (type) {
			case "res": return new A_ResponseParser();
			case "err": return new A_ErrorParser();
			case "feedback" return new A_FeedbackParser();
		}
		return null;
	}
}
public class B_ParserFactory implements AbstractParserFactory{
	@Override
	public Parser getParserInstance(string type){
		switch (type) {
			case "res": return new B_ResponseParser();
			case "err": return new B_ErrorParser();
			case "feedback" return new B_FeedbackParser();
		}
		return null;
	}
}

인터페이스를 통해서 각 팩토리가 제공해야 하는 기능을 명시하고, 실제 팩토리에서는 해당 인터페이스에 맞는 구상클래스를 만들어 제공하도록 하는 것이다.

 

이렇게 만든 팩토리들을 실제 Client에서는 다음과 같이 사용할 수 있을 것이다.

public class ResponseDisplayService extends DisplayService {
	@Override
	public Parser getParser(String type) {
		AbstractParserFactory parserFactory = null;
		switch (type) {
			case 'A': parserFactory = new A_ParserFactory();
			case 'B': parserFactory = new B_ParserFactory();
		}
		return parserFactory.getInstance('res');
	}
}

달라진게 없어 보일 수도 있다. 하지만, 더 이상 Client는 필요한 구상 클래스 생성에 대한 책임이 없어졌다는 것에 주목해야 한다. Parser 팩토리만 생성할 뿐, 사용할 실제 오브젝트의 생성은 모두 각자의 팩토리에서 수행된다. 

 

여기까지가 Abstract Factory Method Pattern을 적용시킨 결과물이다.

 

마지막으로 보여줄 코드는 선택적인 사항이다. 이전 과정까지 수행하면서 Client는 자신이 필요한 구상 클래스의 생성에 대한 책임에서 완전히 벗어났지만, 팩토리 선택에 대한 코드가 남아있다.  이 코드는 모든 DisplayService의 하위 클래스에 공통적으로 존재할 것이다. 이 코드를 한 곳에서 관리하게 하기 위해서 아래와 같은 방식을 사용할 수 있다.

public final class ParserFactoryProducer {
	private AbstractFactoryProductor {
		throw new AssertionError();
	}
    
	public static AbstractParserFactory getFactory(String factoryType) {
		switch (factoryType) {
			case "A": return new A_ParserFactory();
			case "B": return new B_ParserFactory();
		}
		return null;
	}
}

이 ParserFactoryProducer를 이용하면 아래와 같이 DisplayService의 하위 클래스 코드를 간결하게 유지할 수 있다.

public class ResponseDisplayService extends DisplayService {
	@Override
	public Parser getParser(String type) {
		AbstractParserFactory parserFactory = ParserFactoryProducer.getFactory(type);
		return parserFactory.getInstance('res');
	}
}

 

장단점

추상클래스의 장점은 명확한 것 같다.

구상 클래스 생성에 대한 책임을 Client에서 Factory로 위임할 수 있다.

이로 인해서 결합도가 낮아지고, SRP와 OCP를 준수한 설계를 가능하게 하는 효과를 가져온다.

 

하지만, 패턴 구성을 위한 추가적인 인터페이스와 클래스들의 생성으로 설계가 복잡해질 수 있다는 단점은 존재한다.