추상 팩토리 패턴 ( 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)


댓글 펼치기 👇
  1. 오태웅 2019.05.16 10:42

    오답입니다

    • Favicon of https://victorydntmd.tistory.com victolee 우르르응 2019.05.18 13:52 신고

      실행 시 오류가 발생한다는 말씀이신가요? 개념 또는 설계, 예제가 잘못되었다는 말씀이신가요~?

      오류가 발생한 부분은 오타가 있어서 수정되었습니다. 제보 감사드려요~

  2. Favicon of https://perfectacle.github.io/ 지나가던 나그네 2019.06.02 17:30

    실무에서 이런 패턴을 적용할 정도의 실력이 되지 않아서 작성해보지 않았지만 언젠간 작성해볼 날이 오겠죠?
    (개인적으로는 처음부터 너무 복잡도를 높여놓으면(관심사를 분리하는 것도 좋지만 클래스를 많이 만드는 것 또한 복잡도를 높인다고 생각하고 있습니다)
    제 수준에서는 이해하기 힘든 코드가 되더라구요 ㅠㅠ... 이 코드, 저 코드 추적 하다보면 내가 어느 단계까지 왔지... 이 클래스는 뭘 위해 존재하는 클래스이지, 어느 정도까지는 하나로 퉁쳐놓았으면 안될까... 하는 생각도 많이 들고)

    제가 이해한 걸 바탕으로 써볼텐데 제가 제대로 이해한 게 맞는지 검증해주신다면 감사하겠습니다!

    1. 여러 객체를 만들어야하고, 그 객체가 일관된 조건으로 생성될 때, 팩토리 메서드 패턴을 사용하면 팩토리 클래스(MouseFactory, KeyboardFactory)를 각각의 객체(Mouse, Keyboard) 별로 다 작성해줘야하는 문제점을 가진다.
    (이는 결국 일관된 조건으로 객체들을 만들어야하는데 일관되지 않은 조건으로 객체를 만들 수 있는 여지를 제공해주기도 한다.)
    2. 추상 팩토리 패턴을 사용하면 생성해야하는 객체(Mouse, Keyboard) 별로 팩토리 클래스(MouseFactory, KeboardFactory)를 만들지 않고, 일괄적으로 객체를 생성하는 상위 팩토리 클래스(ComputerFactory)만 있으면 된다. (결국 개별 객체를 따로 생성할 수 없고, 하나의 팩토리 클래스를 통해서만 생성할 수 있으므로 무조건 일관된 조건의 클래스들만 생성된다.)

    • Favicon of https://victorydntmd.tistory.com victolee 우르르응 2019.06.03 08:27 신고

      저 또한 주니어 개발자이기에 다른 분의 코드를 읽는게 대부분입니다ㅎㅎ
      그런데 패턴이 적용된 코드를 보거나, 주제가 오고갈때 이해하지 못한 부분이 있어서 공부를 했을 뿐이에요.
      디자인 패턴을 공부하면서 클래스가 너무 많아져서 오히혀 더 복잡도를 증가시킨다는 의견에 동감합니다.
      그래서 무분별한 패턴적용은 오히려 유지보수를 어렵게 만들어서 적절하게 사용하다는게 좋다고 합니다!

      말씀해주신 부분과 제가 이해한것이 일치합니다.
      팩토리 매서드 패턴은 구성품들을 생성하는 팩토리가 있기 때문에 컴퓨터를 생산하려고 하면 구성품들을 일일이 생성해야 합니다.
      추상팩토리 패턴은 이를 캡슐화한 것으로 구성품들이 패키징?되어 컴퓨터를 생성하도록 의도한 패턴이라고 보시면 됩니다.

  3. Favicon of https://atelier-enyou.tistory.com 연유 2019.10.14 22:41 신고

    글들이 굉장히 이해하기 쉽게 적혀있습니다.
    감사합니다.

  4. 지나가다 2020.01.28 13:32

    2. 추상 팩토리 패턴 적용을 설명하실때 사용한 다이어그램에
    인터페이스인 ComputerFactory에 함수로
    createComputer가 아닌 createKeyboard를 적으셨어야 하는데 오타가 난거 같습니다.
    다른 분들이 헷갈리지 않도록 수정함이 좋을거 같아요.
    써주신 글들 감사히 잘 읽고 있습니다 덕분에 개념 이해가 잘 되네요 감사합니다.

  5. 이해가필요합니다. 2020.05.18 05:58

    위의 추상 팩토리 패턴과 팩토리 메소드 패턴의 비교를 보았는데 이해가 가지 않아 질문드립니다.
    둘다 팩토리에서 생성해주는 부분은 같은데 무슨 차이인지 모르겠습니다.
    팩토리 메소드 패턴은 마우스 팩토리, 모니터 팩토리로 나눠 놓은 것이고, 추상 팩토리 패턴은 삼성 컴퓨터 팩토리, LG 컴퓨터 팩토리로 나눠 놓은 것인데 애초에 팩토리 메소드 패턴에서도 마우스, 모니터가 아닌 삼성 컴퓨터 팩토리, LG 컴퓨터 팩토리로 나눠 놓으면 결과는 같은게 아닌가 싶은데 제가 잘못 이해하고 싶은지 도움을 구하고 싶습니다.

    결론적으로 추상 팩토리 패턴은 제품군으로 팩토리를 나눈것이고, 팩토리 메소드 패턴은 객체별로 팩토리를 나눈것이다.
    라고 이해하면 되는걸까요?

  6. hyk 2020.12.16 19:04

    수업 자료 보고서는 이해가 잘 안됐는데 정리 정말 잘해주시네요 감사드립니다 :)