본문 바로가기
소프트웨어공학/Design Pattern

추상 팩토리 (abstract factory Pattern )

by 아이티.파머 2022. 12. 15.
반응형

추상 팩토리 (abstract factory Pattern )

추상 팩토리 패턴은 객체 생성시 구체적인 특정클레스에 의존하지 않고 추상화된 객체를 의존함으로 결합도를 낮춰주고 기존 코드를 수정하지 않고(최소한의 수정으로) 상황에 따라 필요한 객체를 생성하여 사용 할 수 있다.

특징

  • 새로운 모듈이나 기능이 추가되었을대 기존에 작성하였던 코드를 손대거나 수정하지 않고 추상팩토리로 상속받아 모듈을 구현함으로 새로운 기능을 추가할수있다.
  • 추상화한 객체를 의존함으로 결합도가 높은 클레스의 결합도를 낮춰주고 느슨하게 만들어 준다.

추상팩토리패턴은 메서드팩토리패턴과 전혀 성격이 다르다. 또한 추상팩토리 패턴이 메소드팩토리 패턴보다 상위기념이거나 호환할수있는 개념이 아니라는것을 알아두고 가자

  • 팩토리 메소드 패턴
    • 인스턴스를 만드는데 과정이 집중되어 있다.
    • 팩토리를 구현하는 방법(inheritance) 에 초점이 맞춰져 있다.
  • 추상팩토리 :
    • 클라이언트 입장에서 추상화된 객체를 통해 객체를 생성할수있도록 해준다.
    • 팩토리를 사용하는방법(compositoin)에 초점이 맞춰져 있다.

추상팩토리 패턴은 구체적인 클레스에 의존하지 않고 추상화된객체와 인터페이스를 통해 객체의 조합을 만들어내기 쉽게만든 팩토리를 제공하는 패턴이고 팩토리메소드 패턴은 객체를 생성하기위한 인터페이스를 정의하는 과정에서 *어떤 클레스를 만들지 서브클레스에세 결정하도록* 하는 것이다.

예제 )

앞서 팩토리메소드패턴에서 피자를생성할때를 예를들어 설명하였다. 이번에도 피자생성시 추상팩도리패턴을 통해 어떻게 생성하고 활용할수있는지 알아보도록 하자.

기존에 팩토리매소드 패턴에서는 장사가 잘되어 피자가게를 지점별로 두고 피자를 생성하는것에 초점이 맞춰져 있었다. 이번에는 동네장사보다 더 더더 잘되어서 지역별로 피자가게를 두고있다. 지역별 피자가게 이다보니 재료를 조달하는 문제가발생되었다.

  • 뉴욕에는 뉴욕에서 조달가능한 재료를, 시카고에서는 시카고에서 조달가능한 재료를 사용하여야한다.
  • 또한 피자 지점마다 마음대로 재료를 만들어 쓰다보니 품질이 떨어지는곳도 생겼다.

단순히 이익을 취하기위해 그런것이지만 브렌드에 문제를 잃으키게되어 버린다. 이에 재료 공장을 만들어 피자에 들어가는 재료들을 모아 셋트화 시키고 재료공장에서 만든 재료를 조달하도록 하는것이다. 그럼 추가적인 지역이 나타나도 지역별로 재료 공장을 두었기 때문에 재료를 조달하는것에 품질적인 문제가 생기지 않을것이다.

우리는 이것을 해결하기위해 재료공장을 짓기로 했음으로 재료공장에서 만들어야할 것을 뽑아 관련된 상품 셋트를 추려내보자.

재료는 도흐, 치즈, 페페로니, 소스이며 세부 구현은 하위에서 상속받아 진행한다.

// 치즈
public interface Cheese {
    String name();
}

public class MozzarellaCheese implements Cheese {
    @Override
    public String name() {
       return"Mozzarella Cheese";
    }
}

public class ReggianoCheese implements Cheese {
    @Override
    public String name() {
        return "Reggiano Cheese";
    }
}

// 도흐 
public interface Dough {
    String  name();
}

public class ThickCrustDough implements Dough {
    @Override
    public String name() {
        return "thick crust dough";
    }
}

public class ThinCrustDough implements Dough {
    @Override
    public String name() {
        return "thin crust dough";
    }
}

// 페퍼로니
public interface Pepperoni {
    String name();
}

public class SlicedPepperoni implements Pepperoni {
    @Override
    public String name() {
       return "sliced pepperoni";
    }
}

//소스 
public interface Sauce {
    String name();
}

public class PlumtomatoSauce implements Sauce {
    @Override
    public String name() {
        return "Plumtomato Sauce";
    }
}

public class MarinaraSauce implements Sauce {
    @Override
    public String name() {
        return "Marinara Sauce";
    }
}

그림으로 나타내면 다음과 같다. 각 재료별로 추상화하고 실제 구현되는 부분은 상위클레스를 상속받아 구현한다. (페페로니의 경우엔 얇게썰은 페페로니재료만 사용한다.)

재료를 만들어 내는공장을 추상화 시키자.

피자 재료공장에서 준비해야 할 재료 부분을 공통으로 뽑아 추상화 하였다. 반환값으로는 재료들의 상위클레스를 사용한다.

public interface PizzaIngredientFactory {
    // 도흐
    Dough createDough();

    // 치즈
    Cheese createCheese();

    // 소스
    Sauce createSauce();

    // 페페로니
    Pepperoni createPepperoni();

    // 채소
    Veggies[] createVeggies();
}

지점별 재료공장을 만들어낸다.

재료공장의 인터페이스를 상속받아 재료공장을 만들어낸다.

/**
 * 
 * 시카고 재료공장
 * @author skan
 * @since 2022/12/14
 */
public class ChicagoIngredientFactory implements  PizzaIngredientFactory {
    @Override
    public Dough createDough() {
        return new ThickCrustDough();
    }

    @Override
    public Cheese createCheese() {
        return new ReggianoCheese();
    }

    @Override
    public Sauce createSauce() {
        return new PlumtomatoSauce();
    }

    @Override
    public Pepperoni createPepperoni() {
        return new SlicedPepperoni();
    }

    @Override
    public Veggies[] createVeggies() {
        return new Veggies[0];
    }
}

/**
 * 뉴욕시의 재료공장
 * @author skan
 * @since 2022/12/14
 */
public class NYPizzaIngredientFactory implements PizzaIngredientFactory {

    @Override
    public Dough createDough() {
        return new ThinCrustDough();
    }

    @Override
    public Cheese createCheese() {
        return new MozzarellaCheese();
    }

    @Override
    public Sauce createSauce() {
        return new MarinaraSauce();
    }

    @Override
    public Pepperoni createPepperoni() {
        return null;
    }

    @Override
    public Veggies[] createVeggies() {
        return new Veggies[0];
    }
}

재료공장의 UML

재료공장을 만들어 났으니, 이제 피자를 어떻게 생성하고 어떤 피자를 판매할지 만들어보자

Pizza 라는 최상위 추상클레스를 선언하고 피자를 만들 템플릿을 생성한다. 각각의 피자 (채소, 치즈, 페퍼로니)는 최상위 클레스인 Pizza 를 상속받아 CheesPizza, VeggiePizza, PepperoniPizza 를 생성한다.

public abstract class Pizza {

    String name;
    Dough dough;
    Sauce sauce;
    Veggies veggies[];
    Cheese cheese;
    Pepperoni pepperoni;
    Clams clam;

    abstract public void prepare();

    public void bake() {
        System.out.println("Bake for 25 minutes at 350");
    }

    public void cut() {
        System.out.println("Cutting the pizza into diagonal slices");
    }

    public void box() {
        System.out.println("Place pizza in official PizzaStore box");
    }
}

public class VeggiePizza extends Pizza {

    PizzaIngredientFactory factory;

    public VeggiePizza(PizzaIngredientFactory factory) {
        this.factory = factory;
    }

    @Override
    public void prepare() {
        Dough dough   = factory.createDough();
        Sauce sauce   = factory.createSauce();
        Cheese cheese = factory.createCheese();

        System.out.println("PepperoniPizza prepare : "+dough.name());
        System.out.println("PepperoniPizza prepare : "+sauce.name());
        System.out.println("PepperoniPizza prepare : "+cheese.name());
    }
}

public class PepperoniPizza extends Pizza {

    final private PizzaIngredientFactory pizzaIngredientFactory;

    public PepperoniPizza(PizzaIngredientFactory pizzaIngredientFactory) {
        this.pizzaIngredientFactory = pizzaIngredientFactory;
    }

    @Override
    public void prepare() {

        Dough dough   = pizzaIngredientFactory.createDough();
        Sauce sauce   = pizzaIngredientFactory.createSauce();
        Cheese cheese = pizzaIngredientFactory.createCheese();

        System.out.println("PepperoniPizza prepare : "+dough.name());
        System.out.println("PepperoniPizza prepare : "+sauce.name());
        System.out.println("PepperoniPizza prepare : "+cheese.name());

    }
}

public class CheesePizza extends Pizza {

    PizzaIngredientFactory pizzaIngredientFactory;

    public CheesePizza(PizzaIngredientFactory pizzaIngredientFactory) {
        this.pizzaIngredientFactory = pizzaIngredientFactory;
    }

    @Override
    public void prepare() {
        Dough dough   = pizzaIngredientFactory.createDough();
        Sauce sauce   = pizzaIngredientFactory.createSauce();
        Cheese cheese = pizzaIngredientFactory.createCheese();

        System.out.println("CheesePizza prepare : "+dough.name());
        System.out.println("CheesePizza prepare : "+sauce.name());
        System.out.println("CheesePizza prepare : "+cheese.name());
    }

}

그림으로 보면 다음과 같다.

 

abstract class 인 Pizza 에는 반드시 서브클레스에서 생성해주어야할 prepare() 와 hook method 인 bake, box, cut 을가지고 있다. 또한 실제 구현체인 서브클레스들에는 위에서 만들었던 재료공장을 어떤 재료공장을 이용할지에 대해 의존하고 있음으로 구현클레스를 인스턴스화 할때 생성자에 NYIngredientFactory 혹은 ChicagoPizzaIngredientFactory 를 생성자로 넣어서 보내준다.

만들어질 OOOPizza 클레스에서 prepare 메소드를 오버라이딩하여 구현할때 생성자에서 들어온 IngradientFactory 를 활용하여 피자 생성시 필요한 재료들을 각 피자에 맞게 재료들을 생성해준다.

 

 

어떤 피자를 만들지 정했으니 이제 피자를 만들 피자공장을 만들자. 피자공장 또한 지점별로 만들어야하기에 추상클레스로 PizzaStore를 두고 최상위 클레스를 상속받아 각각 뉴욕과, 시카고의 피자생산공장을 만들어 낸다.

이전에서 피자생성시 생성자에 재료공장이 의존되어 있음으로 피자를 생성하는 공장(Store)에서는 특정 재료공장팩토리를 생성하여 OOO피자객체 생성시 생성자에 포함하여 구현하도록 한다.

/**
 * 추상화 생성공장 
 * @author skan
 * @since 2022/12/14
 */
public abstract class PizzaStore {
    public Pizza order (String name) {

        Pizza pizza = this.createPizza(name);
        pizza.prepare();
        pizza.bake();
        pizza.cut();
        pizza.box();

        return pizza;

    }

    abstract Pizza createPizza(String name) ;
}

public class NYPizzaStore extends PizzaStore {

    // 피자 생성시 어떤 재료공장의 팩토리를 사용할지 선택한다.
    PizzaIngredientFactory pizzaIngredientFactory = new NYPizzaIngredientFactory();

    @Override
    Pizza createPizza(String name) {

        Pizza pizza = null;
        if (name.equals("cheese")) {
            pizza = new CheesePizza(pizzaIngredientFactory);
        } else if (name.equals("veggie")) {
            pizza = new VeggiePizza(pizzaIngredientFactory);
        } else if (name.equals("pepperoni")) {
            pizza = new PepperoniPizza(pizzaIngredientFactory);
        } else {
            throw new IllegalArgumentException();
        }
        return pizza;
    }
}

public class ChicagoPizzaStore extends PizzaStore{

    // 피자 생성시 어떤 재료공장의 팩토리를 사용할지 선택한다.
    final PizzaIngredientFactory pizzaIngredientFactory = new ChicagoIngredientFactory();

    @Override
    Pizza createPizza(String name) {

        Pizza pizza = null;
        if (name.equals("cheese")) {
            pizza = new CheesePizza(pizzaIngredientFactory);
        } else if (name.equals("veggie")) {
            pizza = new VeggiePizza(pizzaIngredientFactory);
        } else if (name.equals("pepperoni")) {
            pizza =  new PepperoniPizza(pizzaIngredientFactory);
        } else {
            throw new IllegalArgumentException();
        }

        return pizza;
    }
}

그림으로 보면 다음과 같다. 필드에 pizzaIngredientFactory 를 각자 사용할 재료공장의 인스턴스를 생성한다.

어떤 피자를 만들어 낼지 createPizza 메소드에 구현한다.

 

이로써 재료공장에대한 팩토리와 피자를생성하는 팩토리를 분리하였고, 각각의 공통점을 추려내여 추상화 하였다.

  • 재료공장의 추상화
  • 피자생성의 추상화
  • 피자스토어의 추상화

이제 그럼 왜 이렇게 추상화하여 사용해야 하는지에대해 궁금할것이고 뭐가 좋은지 알아보도록 하자, 해당패턴을 사용하면 장사가 잘되어 새로운 분점을 만들고자 할때 신규 피자스토어를 추가하기만 하면 된다. 즉 켈리포니아지점이 새로 만들어진다면 다른 코드들을 수정하지 않고 켈리포니아 관련 공장들을 세우면 되는것이다.

즉 추상화된 팩토리와, 인터페이스를 상속받아 Califonia 관련된 클레스들을 생성하고 기능을 구현 할 수 있게 된다.

  • 기존 코드에 는영향을 주지 않으면서 새로운 추가할수있게 된다. (객체지향 5대원칙중 개방폐쇠의원칙)
  • 재료공장을 추상화하여 상속받은 곳에서 필요한 기능만 구현하도록 되었다. (인터페이스분리의 원칙, 단일책임의 원칙)
  • 재료공장을 추상화하여 피자생성시 필요한 공장(뉴욕,시카고 의)을 사용 할 수있다. (리스코프치환의 원칙, 의존성 역전의 원칙)

객체지향의 5대원칙도 구현하면서 확인할수있다.

개인적으로 디자인패턴을 공부하면서 느끼는게 있다면, 업무에 대한 이해도가 없다면 디자인패턴을 아무리잘알고 있어도 적용하기 어렵다는것이다. 여러 업무들을 겪으면서 공통부분을 추상화할 수 있는 능력을 기르고 디자인패턴도 공부한다면 이 책에서 말하는것처럼 어떤 업무를 할때 어떤 패턴을 적용해야 할지 고를수있는 안목을 가지게 될것이다.

 

참조

Head First Design Patterns

[Design pattern]개발 현장에서 많이 쓰는 14가지 디자인 패턴의 종류와 정의

반응형