추상 팩토리 패턴 ( Abstract Factory Pattern )

추상 팩토리 패턴이라는 이름만 봐서는 팩토리 메서드 패턴과 비슷해보이지만, 명확한 차이점이 있습니다.


  • 팩토리 메서드 패턴
    • 조건에 따른 객체 생성을 팩토리 클래스로 위임하여, 팩토르 클래스에서 객체를 생성하는 패턴 ( 링크 )
  • 추상 팩토리 패턴
    • 서로 관련이 있는 객체들을 통째로 묶어서 팩토리 클래스로 만들고, 이들 팩토리를 조건에 따라 생성하도록 다시 팩토리를 만들어서 객체를 생성하는 패턴

추상 팩토리 패턴은 어떻게 보면, 팩토리 메서드 패턴을 좀 더 캡슐화한 방식이라고 볼 수 있습니다.


예를 들어, 컴퓨터를 생산하는 공장이 있을 때, 마우스, 키보드, 모니터의 제조사로 Samsung과 LG가 있다고 가정하겠습니다.

컴퓨터를 생산할 때 구성품은 전부 삼성으로 만들거나, 전부 LG로 만들어야겠죠?

키보드, 모니터는 Samsung인데, 마우스만 LG면 안되겠죠....


이렇게 컴퓨터는 같은 제조사인 구성품들로 생산되어야 합니다.

다시 말하면, SamsungComputer 객체는 항상 삼성 마우스, 키보드, 모니터 객체들이 묶여서 생산되어야 합니다.

즉, 객체를 일관적으로 생산해야 할 필요가 있습니다.


또한 코드 레벨에서 보면, SamsungComputer인지 LGComputer인지는 조건에 따라 분기될 것이기 때문에

팩토리 메서드 패턴과 같이, 조건에 따라 객체를 생성하는 부분을 Factory 클래스로 정의할 것입니다.



대충 이 정도의 감을 잡고 코드를 보도록 하겠습니다.

먼저, 팩토리 메서드 패턴을 사용하여 컴퓨터를 생산하는 로직을 구현해보도록 하겠습니다.


주의할 것은 추상 팩토리 패턴이 팩토리 메서드 패턴의 상위호환이 아니라는 것입니다.

두 패턴의 차이는 명확하기 때문에 상황에 따라 적절한 선택을 해야할 것입니다.





1. 추상 팩토리 패턴 사용 이유 - 팩토리 메서드 패턴을 사용할 경우의 문제

팩토리 메서드 패턴을 사용하여, 컴퓨터를 생산하는 로직을 구현해보도록 하겠습니다.

클래스가 많아서 조금 복잡해보일 수 있는데, 로직은 동일하니 크게 어려운건 없습니다.



1)

먼저 키보드 관련 클래스들을 정의하겠습니다.

LGKeyboardSamsugKeyboard 클래스를 정의하고, 이를 캡슐화하는 Keyboard 인터페이스를 정의합니다.

그리고 KeyboardFactory 클래스에서 입력 값에 따라 LGKeyboard 객체를 생성할지, SamsungKeyboard를 생성할지 결정합니다.

public class LGKeyboard implements Keyboard {
public LGKeyboard(){
System.out.println("LG 키보드 생성");
}
}
public class SamsungKeyboard implements Keyboard {
public SamsungKeyboard(){
System.out.println("Samsung 키보드 생성");
}
}

public interface Keyboard {
}

public class KeyboardFactory {
public Keyboard createKeyboard(String type){
Keyboard keyboard = null;
switch (type){
case "LG":
keyboard = new LGKeyboard();
break;

case "Samsung":
keyboard = new SamsungKeyboard();
break;
}

return keyboard;
}
}


2)

Keyboard와 동일하게 Mouse 관련 클래스들을 정의합니다.

public class LGMouse implements Mouse {
public LGMouse(){
System.out.println("LG 마우스 생성");
}
}
public class SamsungMouse implements Mouse {
public SamsungMouse(){
System.out.println("Samsung 마우스 생성");
}
}

public interface Mouse {
}

public class MouseFactory {
public Mouse createMouse(String type){
Mouse mouse = null;
switch (type){
case "LG":
mouse = new LGMouse();
break;

case "Samsung":
mouse = new SamsungMouse();
break;
}

return mouse;
}
}


3)

다음으로 ComputerFactory 클래스를 구현합니다.

ComputerFactory 클래스는 KeyboardFactoryMouseFactory 객체를 생성해서 어떤 제조사의 키보드와 마우스를 생산할 것인지 결정합니다.

public class ComputerFactory {
public void createComputer(String type){
KeyboardFactory keyboardFactory = new KeyboardFactory();
MouseFactory mouseFactory = new MouseFactory();

keyboardFactory.createKeyboard(type);
mouseFactory.createMouse(type);
System.out.println("--- " + type + " 컴퓨터 완성 ---");
}
}


4)

마지막으로 컴퓨터를 생산하기 위한 Client 클래스를 구현합니다.

public class Client {
public static void main(String args[]){
ComputerFactory computerFactory = new ComputerFactory();
computerFactory.createComputer("LG");
}
}



팩토리 메서드 패턴을 사용하여, 컴퓨터를 생산해보았습니다.

그런데 컴퓨터의 구성품은 키보드, 마우스 뿐만 아니라 본체 구성품들, 모니터, 스피커, 프린터 등등 여러가지가 있죠.


위의 코드를 그대로 사용하고자 한다면, 본체팩토리, 모니터팩토리, 스피커팩토리, 프린터팩토리 클래스를 정의해야 하고,

CoumputerFactory에서는 예를 들어, 다음과 같이 각 팩토리 클래스 객체들을 생성해서 컴퓨터가 완성이 될 것입니다.

public class ComputerFactory {
public void createComputer(String type){
KeyboardFactory keyboardFactory = new KeyboardFactory();
MouseFactory mouseFactory = new MouseFactory();
BodyFactory bodyFactory = new BodyFactory();
MonitorFactory monitorFactory = new MonitorFactory();
SpeakerFactory speakerFactory = new SpeakerFactory();
PrinterFactory printerFactory = new PrinterFactory();

keyboardFactory.createKeyboard(type);
mouseFactory.createMouse(type);
bodyFactory.createBody(type);
monitorFactory.createMonitor(type);
speakerFactory.createSpeaker(type);
printerFactory.createPrinter(type);
System.out.println("--- " + type + " 컴퓨터 완성 ---");
}
}

그런데 사실 Samsung 컴퓨터라면 구성품이 모두 Samsung이어야 하고, LG 컴퓨터라면 구성품이 모두 LG인 것이 맞습니다.

즉, 각각의 컴퓨터 구성품들을 Samsung이냐 LG냐 구분할 필요가 없이,

Samsung 컴퓨터를 만들고자 한다면 구성품이 모두 Samsung이 되도록, 일관된 방식으로 객체를 생성할 필요가 있습니다.


또한 구성품이 늘어날수록 팩토리 객체를 생성하는 부분이 더욱 길어지겠죠.


따라서 추상 팩토리 패턴을 적용하여 구성품이 모두 동일한 제조사가 되도록 개선해보겠습니다.





2. 추상 팩토리 패턴 적용

복잡한걸 싫어하지만, 어쩔수 없었습니다...


패턴 적용 전과 비교했을 때의 차이점은 다음과 같습니다.

  • 어떤 제조사의 부품을 선택할지 결정하는 팩토리 클래스( KeyboardFactory, MouseFactory )가 제거되고, Computer Factory 클래스가 추가되었습니다. ( SamsungComputerFactory, LGComputerFactory )
  • SamsungComputerFactory, LGComputerFactory는 ComputerFactory 인터페이스로 캡슐화하고, 어떤 제조사의 부품을 생성할 것인지 명확하므로, 각각의 제조사의 부품을 생성합니다. ( 일관된 객체 생성 )

  • FactoryOfComputerFactory 클래스에서 컴퓨터를 생산하는 createComputer() 메서드를 호출합니다.


이제 코드를 살펴보겠습니다.

Keyboard, LGKeyboard, SamsungKeyboard, Mouse, LGMouse, SamsungMouse 클래스는 이전과 코드가 동일합니다.



1)

먼저 SamsungComputerFactory, LGComputerFactory 클래스를 정의하고, 이들을 캡슐화하는 ComputerFactory 인터페이스를 정의합니다.


각 클래스는 자신의 제조사 부품 객체를 생성합니다.

예를 들어, SamsungComputerFactory 클래스는 삼성 키보드, 마우스를 가지므로 SamsungKeyboard, SamsungMouse 객체를 생성합니다.

public class SamsungComputerFactory implements ComputerFactory {
public SamsungKeyboard createKeyboard() {
return new SamsungKeyboard();
}

public SamsungMouse createMouse() {
return new SamsungMouse();
}
}
public class LGComputerFactory implements ComputerFactory {
public LGKeyboard createKeyboard() {
return new LGKeyboard();
}

public LGMouse createMouse() {
return new LGMouse();
}
}
public interface ComputerFactory {
public Keyboard createKeyboard();
public Mouse createMouse();
}


2)

다음으로 FactoryOfComputerFactory 클래스를 정의합니다.

이 클래스는 패턴 적용 전 ComputerFactory 클래스와 하는일이 같습니다.


입력값에 따라 객체 생성을 분기하는데요, 이 때 어떤 제조사 컴퓨터 객체를 생성할지 결정합니다.

즉, 부품이 아니라 컴퓨터 객체를 생성한다는 점에서 차이점이 있습니다.


public class FactoryOfComputerFactory {
public void createComputer(String type){
ComputerFactory computerFactory= null;
switch (type){
case "LG":
computerFactory = new LGComputerFactory();
break;

case "Samsung":
computerFactory = new SamsungComputerFactory();
break;
}

computerFactory.createKeyboard();
computerFactory.createMouse();
}
}


3)

마지막으로 컴퓨터를 생산하기 위한 Client 클래스를 정의합니다.

public class Client {
public static void main(String args[]){
FactoryOfComputerFactory factoryOfComputerFactory = new FactoryOfComputerFactory();
factoryOfComputerFactory.createComputer("LG");
}
}

결과는 이전과 동일합니다.





이상으로 추상 팩토리 패턴이 무엇인지에 대해 알아보았습니다.

정리 하면, 패턴 적용 전 ( 팩토리 메서드 패턴 )에서는 구성품 마다 팩토리를 만들어서 어떤 객체를 형성했는데 그 객체의 구성품은 일정하므로, 

추상 팩토리 패턴을 적용하여 관련된 객체들을 한꺼번에 캡슐화 하여 팩토리로 만들어서 일관되게 객체를 생성하도록 했습니다.


Java에서도 여러 디자인 패턴을 사용하고 있는데 추상 팩토리 패턴을 사용하고 있는 API는 다음과 같으며, 참고하시면 좋을 것 같습니다. ( 참고링크 )

Abstract factory

(recognizeable by creational methods returning the factory itself which in turn can be used to create another abstract/interface type)