코딩하는 털보

디자인 패턴 4. 팩토리 패턴 2/2 본문

Book/헤드 퍼스트 디자인 패턴

디자인 패턴 4. 팩토리 패턴 2/2

이정인 2021. 9. 3. 12:44

디자인 패턴 4. 팩토리 패턴

디자인원칙, 추상화된 것에 의존하도록 만들어라. 구상 클래스에 의존하도록 만들지 않도록 한다. (Dependency Inversion Principle)
위 원칙은 고수준 구성요소가 저수준 구성요소에 의존하면 안 된다는 것이 내포되어 있다.

예를 들면 처음에 예로 나왔던 PizzaStore는 고수준 구성요소이며 여러가지 저수준 구성요소인 Pizza 클래스들에 의존하고 있다.

public class PizzaStore {

    Pizza orderPizza(String type) {
        Pizza pizza;

        if (type.equals("cheese")) {
            pizza = new CheesePizza();
        } else if ( type.equals("pepperoni")) {
            pizza = new PepperoniPizza();
        } else if ( type.equals("clam")) {
            pizza = new ClamPizza();
        } else {
            throw new NoSuchElementException();
        }

        pizza.prepare();
        pizza.bake();
        pizza.cut();
        pizza.box();
        return pizza;
    }
}

의존성 뒤집기 원칙에 따르면 구상 클래스처럼 구체적인 것이 아닌 추상 클래스나 인터페이스와 같이 추상적인 것에 의존하는 코드를 만들어야 한다.
위의 PizzaStore는 팩토리 메서드 패턴을 이용해서 인스턴스를 만드는 부분을 뽑아내어 추상 클래스 Pizza에만 의존하게 할 수 있었다. 게다가 다른 저수준 구성요소인 콘크리트 Pizza 클래스들도 추상 클래스 Pizza에 의존하게 된다.
팩토리 메서드 패턴은 의존성 뒤집기 원칙을 준수하기 위해 가장 적합한 방법 중 하나이다.

의존성 뒤집기 원칙 지키는 규칙

  • 어떤 변수에도 구상 클래스에 대한 레퍼런스를 저장하지 않는다.
  • 구상 클래스에서 유도된 클래스를 만들지 않는다.
  • 베이스 클래스에 이미 구현되어 있던 메서드를 오버라이드 하지 않는다.

이 규칙을 항상 지켜야하는것은 아니지만 객체지향 원칙을 지키기 위해서 지향하는 방향이 되어야 한다.
예를 들면 String 클래스 같은 경우 변할 일이 거의 없을 테니 위의 규칙을 무시하고 그냥 쓰는 것이다.


추상 팩토리 패턴
다음 예는 지역별 피자 가게에서 서로 다른 원재료를 사용한다. 원재료군(도우, 소스, 치즈, 야채, 페퍼로니, ...) 별 추상 클래스와 원재료 추상 팩토리 및 구현 클래스를 만든다.
원재료를 생성하는 각 create 메서드들이 모두 팩토리 메서드 역할을 한다. 사실 그냥 추상 팩토리 안에서 팩토리 메서드 패턴을 사용한 것이다.

public interface PizzaIngredientFactory {
    public Dough createDough();
    public Sauce createSauce();
    public Cheese createCheese();
    public Veggies[] createVeggies();
    public Pepperoni createPepperoni();
    public Clams createClam();
}
public class NYPizzaIngredientFactory implements PizzaIngredientFactory{
    @Override
    public Dough createDough() {
        return new ThinCrustDough();
    }

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

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

    @Override
    public Veggies[] createVeggies() {
        return new Veggies[]{new Garic(), new Onion(), new Mushroom()};
    }

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

    @Override
    public Clams createClam() {
        return new FreshClams();
    }
}

Pizza 클래스가 원재료를 사용하도록 변경했다.

public abstract class Pizza {
    String name;
    Dough dough;
    Sauce sauce;
    Veggies[] veggies;
    Cheese cheese;
    Pepperoni pepperoni;
    Clams clams;

    abstract void prepare();

    void bake() {
        System.out.println("25-30분 굽기.");
    }

    void cut() {
        System.out.println("8조각으로 자르기.");
    }

    void box() {
        System.out.println("박스에 넣기.");
    };

    String getName() {
        return name;
    }

    void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "name='" + name + '\'';
    }
}

이제 지역별로 피자 클래스를 만드는 대신 지역별 원재료 공장에서 재료를 수급하는 것으로 Pizza 클래스와 PizzaStore들을 변경한다.
Pizza 클래스는 원재료 객체들에 의존하는 대신 추상 팩토리에 의존한다.

public class CheesePizza extends Pizza{

    PizzaIngredientFactory ingredientFactory;

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

    @Override
    void prepare() {
        System.out.println("재료 준비.");
        //재료 공장의 팩토리 메서드에 객체 생성을 위임한다.
        //CheesePizza는 무슨 재료를 가져오는지 알 수 없다.
        dough = ingredientFactory.createDough();
        cheese = ingredientFactory.createCheese();
        sauce = ingredientFactory.createSauce();
    }
}
public class NYPizzaStore extends PizzaStore{
    Pizza pizza;
    final PizzaIngredientFactory ingredientFactory = new NYPizzaIngredientFactory();

    @Override
    public Pizza createPizza(String type) {
        if (type.equals("cheese")) {
            pizza = new CheesePizza(ingredientFactory);
            pizza.setName("New York Style Cheese Pizza");
        } else if ( type.equals("pepperoni")) {
            pizza = new PepperoniPizza(ingredientFactory);
            pizza.setName("New York Style Pepperoni Pizza");
        } else if ( type.equals("clam")) {
            pizza = new ClamPizza(ingredientFactory);
            pizza.setName("New York Style Clam Pizza");
        } else {
            throw new NoSuchElementException();
        }
        return pizza;
    }
}

추상 팩토리 패턴에서는 인터페이스를 이용하여 서로 연관된, 또는 의존하는 객체를 구상 클래스를 지정하지 않고도 생성할 수 있다.

추상 팩토리 패턴과 팩토리 메서드 패턴은 비슷해보이지만 서로 다른 패턴이다.
둘 다 객체 생성을 캡슐화하는 것은 동일하지만 서로 다른 방법을 사용한다.
팩토리 메서드 패턴의 경우 상속을 통해서 객체를 만든다. 구상 생산자에서 구현한 팩토리 메서드를 추상 생산자에서 활용한다. 어떤 구상 생산자를 필요로 하게 될지 미리 알 수 없는 경우 매우 유용하다.
추상 팩토리 패턴은 컴포지션을 통해서 객체를 만든다. 일련의 연관된 제품을 하나로 묶을 수 있다는 장점이 있다. 구상 팩토리를 구현할 때 각 제품의 생성에 대해 팩토리 메서드를 쓰는 경우가 있다.

Comments