본문 바로가기

Design Pattern

[디자인 패턴_Again] Factory Pattern. - (3) -

Abstract Factory Pattern

추상 팩토리 패턴입니다.

이것은 팩토리를 추상화하여 사용하는 방법입니다.

여기서는 각 나라의 햄버거와 샌드위치를 모두 함께 판매하는 total-store를 만들기로

했습니다.

컨셉은 각나라의 햄버거/샌드위치 공장에서 햄버거와 샌드위치를 만들어서 가져와

파는 것입니다.

이것을  전체적인 패턴을 추상 팩토리 패턴으로 구현해보겠습니다.

일단 샌드위치와 햄버거를 생산 할 공장의 인터페이스를 정의하겠습니다.


public interface ProductFactory {
 public Hamburger createHamburger();
 public Sandwich createSandwich();
}


추상 팩토리 패턴에서는 추상화된 공장과 추상화된 상품이 존재합니다.

위의 코드에서는 두개의 메소드가 존재합니다. 햄버거를 만드는 것과 샌드위치를 만드는 것입니다.

각 나라별로 햄버거와 샌드위치가 다르기 때문에 이 인터페이스를 구현 한 구상 팩토리를 만들

어야 합니다.

그러면 실제로 TotalStore에서는 이 공장을 사용해서 원하는 제품을 가져 올 수 있는거죠...

일단 구상 팩토리를 보겠습니다.

public class ChinaProductFactory implements ProductFactory {

 @Override
 public Hamburger createHamburger() {
  return new ChinaBicmacBurger();
 }

 @Override
 public Sandwich createSandwich() {
  return new ChinaSandwich();
 }

}
---------------------------------------------------------------

public class KoreaProductFactory implements ProductFactory {

 @Override
 public Hamburger createHamburger() {
  return new KoreaBicmacBurger();
 }

 @Override
 public Sandwich createSandwich() {
  return new KoreaSandwich();
 }

}

그러면 이것을 사용하는 TotalStore의 코드입니다.

public class TotalStore {
 ProductFactory factory;
 
 public void setFactory(ProductFactory factory) {
  this.factory = factory;
 }

 
 public Hamburger orderHamburger() {
  return factory.createHamburger();
 }
 
 public Sandwich orderSandwich() {
  return factory.createSandwich();
 }
}

위에서 볼드처리된 부분이 핵심입니다.

TotalStore에서는 만들고 싶은 상품에 따라서 팩토리를 교체하여 만들어 낼 수 있습니다.

실제로 햄버거나 샌드위치를 만들때도 결국은 factory에게 그것을 위임시키고 있습니다.

한국빅맥햄버거이던 중국샌드위치이건 그것은 factory만 셋팅이 되면

factory가 알아서 만들어 가져 올 것이고 TotalStore에서는

단지 Hamburger나 Sandwich 형식의 물건이 오면 그것을 믿고

사용하겠다는 것입니다.



햄버거클래스와 샌드위치 클래스를 보겠습니다.

TotalStore에서 공장에 믿고 맡기기 위해서는 최소한 Hamburger형인지 Sandwich형인지는

서로 주고 받을 약속이 되어 있어야 합니다.

그것이 중국식샌드위치인지 한국빅맥햄버거인지까지는 알필요가 없다고 하더라도요..

그래서 , 이 패턴에서는 햄버거와 샌드위치의 인터페이스(추상 클래스를 포함합니다)를

뽑아냅니다.

public abstract class Hamburger {
 String name;
 String meat;
 String bread;
 String source;
 
 public void prepare() {
  System.out.println("재료를 준비합니다..." + name);
 }
 
 public void roastPetty() {
  System.out.println(meat + "를 구워냅니다.");
 }
 
 public void mixStuff() {
  System.out.println(bread + "와 "+meat+"패티와 " + source + "를 사용해 햄버거를 만듭니다.");
 }
 
 public void box() {
  System.out.println("포장을 합니다.");
 }
 
 public void create() {
  prepare();
  roastPetty();
  mixStuff();
  box();
 }
}
--------------------------------------------------------
public class KoreaBicmacBurger extends Hamburger {
 
 public KoreaBicmacBurger() {
  name = "Korea BicMac Burger";
  meat = "한우소고기";
  bread = "쌀빵";
  source = "머스터드";
 
  create();
 }
}



 

public abstract class Sandwich {
 String name;
 String meat;
 String bread;
 String vege;
 
 public void prepare() {
  System.out.println("재료를 준비합니다..." + name);
 }
 
 public void roastPetty() {
  System.out.println(meat + "를 구워냅니다.");
 }
 
 public void mixStuff() {
  System.out.println(bread + "와 "+meat+"패티와 " + vege + "를 사용해 샌드위치를 만듭니다.");
 }
 
 public void box() {
  System.out.println("포장을 합니다.");
 }
 
 public void create() {
  prepare();
  roastPetty();
  mixStuff();
  box();
 }
}
-----------------------------------------------------------
public class ChinaSandwich extends Sandwich {
 public ChinaSandwich() {
  name = "China Sandwich";
  meat = "계란과 햄 믹스";
  bread = "식빵";
  vege = "토마토";
 
  create();
 }
}


 햄버거와 샌드위치 구상 클래스의 생성자에 들어가있는 create() 메서드는 너무 신경쓰지 말아주세요.

그냥 이녀석이 만들어진다. 라는 것을 보여주기 위한 편법일 뿐입니다.

중요한 것은 햄버거와 샌드위치의 인터페이스로부터 구상 클래스가 만들어졌다는 것입니다.

테스트 코드는 아래와 같습니다.

public class Test {
 public static void main(String[] args) {
  TotalStore store = new TotalStore();
  System.out.println("난 한국 빅맥 버거를 먹을거에요");
  store.setFactory(new KoreaProductFactory());
  store.orderHamburger();
 
  System.out.println("난 중국 샌드위치를 먹을거에요");
  store.setFactory(new ChinaProductFactory());
  store.orderSandwich();
 }
}


 

난 한국 빅맥 버거를 먹을거에요
재료를 준비합니다...Korea BicMac Burger
한우소고기를 구워냅니다.
쌀빵와 한우소고기패티와 머스터드를 사용해 햄버거를 만듭니다.
포장을 합니다.

난 중국 샌드위치를 먹을거에요
재료를 준비합니다...China Sandwich
계란과 햄 믹스를 구워냅니다.
식빵와 계란과 햄 믹스패티와 토마토를 사용해 샌드위치를 만듭니다.
포장을 합니다.

재미있는 것은 팩토리부분을 보시면 어디서 많이 보던 형식이 나타납니다.

ProductFactory의
 public Hamburger createHamburger();
 public Sandwich createSandwich();

이 두개의 메서드를 실제 구현하는 부분은 ProductFactory를 구현한

구상 클래스들입니다. 이건 패토리 메소드 패턴이죠^^

추상 팩토리 패턴에는 팩토리 메소드 패턴도 같이 사용이 됩니다.


팩토리 메소드 패턴은 메소드를 추상화시켜 하위에서 구현하도록 한 것이고

추상 팩토리 패턴은 팩토리 자체를 추상화 시켜서 하위 클래스에서 구현을 하도록 한 것입니다.

그렇기 때문에 그 과정에서 자연스럽게 추상 팩토리에 있는 메서드를 구현하게 되고

이 과정에서 팩토리 메소드 패턴이 사용되는 것입니다.


앞의 3개의 팩토리 패턴은 결국 패턴을 이루고 있는 구성의 차이는 있지만

만들어지는 무언가를 추상화 시켜서 (혹은 만드는 무언가를 ) 만드는 부분과

사용하는 부분의 의존성을 최소화 시키려는데

그 목적이 있습니다.

사용하는 부분에서 만들어지는 녀석의 구현 클래스를 가지고 작업을 하고 있으면

구현 클래스의 변경이라든가, 새로운 구현 클래스의 추가라는 작업이 생길 때 마다

사용하는 녀석과 만드는 녀석 모두를 변경해줘야 하는 부담이 생기게 됩니다.


하지만, "나는 니가 한국빅맥버거를 줄지, 중국빅맥버거를 줄지 그것은 잘 모르지만

그게 햄버거라는 것은 알아. 그리고 햄버거라는 것은 나라에 상관 없이 공통의 행동을

가지고 있지. 그리고 난 그 행동이 필요한거야." 라고 추상화를 시켜버리면 나중에

미국식 햄버거가 새로 생겼다 하더라도 그것이 햄버거라는 형식을 가지고 있으면

(extends나 implements 하고 있으면) 사용하는 측의 수정은 전혀 필요없이

단순히 햄버거를 상속 혹은 구현하고 있는 미국빅맥버거만 만들어주면 되는 것입니다.

공장이 추상화 된 경우에는

"나는 니가 한국햄버거와 샌드위치를 만드는 공장인지 중국햄버거와 샌드위치를

만드는 공장인지 잘 모르지만, 아무튼 니가 createHamburger와 createSandwich

메소드를 가진 공장이라는 것은 알고있고, 그것들을 실행하면 내가 원하는 것을

얻을 수 있다는 건 알고있어." 정도가 되겠지요 ^^

미국식 햄버거와 샌드위치를 추가로 팔기로 했다면, 그것을 만드는 공장만 하나

생성해서 사용하면 ok가 되는 것입니다.