Java/Design Pattern

[헤드퍼스트 디자인 패턴] 4. 팩토리 메서드 패턴

noahkim_ 2024. 12. 16. 22:34

에릭 프리먼 님의 "헤드퍼스트 디자인 패턴" 책을 정리한 포스팅 입니다

 

1. 기존 문제: new 키워드

  • new 키워드를 통해 직접 객체 생성 시 → 특정 구현체에 강하게 의존하게 됨
  • ⚠️ OCP, DIP 위반 → 코드 변경에 취약, 테스트 어려움

 

2. 팩토리 메서드 패턴

  • 객체를 생성을 서브클래스에서 위임하는 패턴
  • 클라이언트 코드와 구현체 생성 코드를 분리시킴

 

추상 클래스화

  • 서브클래스에 피자 생성을 위임함
  • 어떤 종류의 피자를 만들지에 대한것은 어떤 서브클래스를 생성했느냐에 따라 결정됨
  • 클라이언트는 어떤 구현이 생성되는지 몰라도 됨

 

예제) PizzaStore

더보기

추상클래스

public abstract class PizzaStore {	
    public Pizza orderPizza(String type) {
        Pizza pizza = createPizza(type);
        
        pizza.prepare();
        pizza.bake();
        pizza.cut();
        pizza.box();
        
        return pizza;
    }
    
    public abstract Pizza createPizza(String type);
}

 

서브클래스

public class NYPizzaStore extends PizzaStore {
    public Pizza createPizza(String item) {
        if (item.equals("cheese") return new NYStyleCheesePizza();
        if (item.equals("veggie") return new NYStyleVeggiePizza();
        if (item.equals("clam") return new NYStyleClamPizza();
        if (item.equals("pepperoni") return new NYStylePepperoniPizza();
        
        return null;
    }
}

 

3. 추상 팩토리 패턴

  • 연관된 여러 객체(제품군)을 생성해야 할 때 사용
  • 제품군 생성을 위한 인터페이스 정의
  • 구체 팩토리는 Composition 방식으로 필요한 구성 요소 생성 (내부 구성의 생성 책임을 외부 팩토리에 위임)

 


예제) Pizza

더보기

추상클래스

public abstract class Pizza {
    String name;
    
    Dough dough;
    Sauce sauce;
    Veggies[] veggies;
    Cheese cheese;
    Pepperoni pepperoni;
    Clams clam;
    
    void setName(String name) { this.name = name; }
    String getName() { return name; }
    
    void bake() { ... }
    void cut()  { ... }
    void box()  { ... }
    abstract void prepare();
}

 

서브클래스

public class CheesePizza extends Pizza {
    PizzaIngredientFactory pizzaIngredientFactory;
    
    public CheesePizza(PizzaIngredientFactory pizzaIngredientFactory) {
        this.pizzaIngredientFactory = pizzaIngredientFactory; 
    }
    
    void prepare() {
        dough = ingredientFactory.createDough(); 
     	sauce = ingredientFactory.createSauce();
        cheese = ingredientFactory.createCheese();
    }
}

 

Factory

  • 호출자는 구체적인 구현을 알 필요 없음
  • 확장에 열려있고 변경에 닫혀있게 할 수 있음

 

예제) PIzzaIngredientFactory

더보기

추상클래스

public interface PizzaIngredientFactory {    
    Dough createDough(); 
    Sauce createSauce();
    Cheese createCheese();
    Veggies[] createVeggies();
    Pepperoni createPepperoni();
    Clams createClam();
}
  • 제품군 구성에 대한 통일된 인터페이스 제공

 

서브클래스

public class NYPizzaIngredientFactory implements PizzaIngredientFactory {
    public Dough createDough() { return new ThinCrustDough(); }
    public Sauce createSauce() { return new MarinaraSauce(); }
    public Cheese createCheese() { return new ReggianoCheese(); }
    public Veggie[] createVeggies() { return new[] {new Galic(), new Onion(), new Mushroom(), new RedPepper() }; }
    public Pepperoni createPepperoni() { return new SlicedPepperoni(); }
    public Clams createClam() { return new FreshClams(); }
}

 

4. DIP (의존성 뒤집기 원칙)

  • 고수준 모듈(정책 결정)은 저수준 모듈(구현 세부사항)에 의존해선 안되며, 둘다 추상화에 의존해야 함

 

문제점: DIP 전

  • 고수준 클래스가 직접 구체 클래스를 생성
  • 새로운 구현체가 생기면 고수준 클래스 수정 필수
  • ⚠️ OCP 위반

예제) DependentPizzaStore

더보기
public class DependentPizzaStore {
    public Pizza createPizza(String style, String type) {
    	Pizza pizza = null;
        
        if (style.equals("NY")) {
            if (item.equals("cheese") return new NYStyleCheesePizza();
            if (item.equals("veggie") return new NYStyleVeggiePizza();
            if (item.equals("clam") return new NYStyleClamPizza();
    	    if (item.equals("pepperoni") return new NYStylePepperoniPizza();
        } else if (style.equals("Chicago")) {
            if (item.equals("cheese") return new ChicagoStyleCheesePizza();
            if (item.equals("veggie") return new ChicagoStyleVeggiePizza();
            if (item.equals("clam") return new ChicagoStyleClamPizza();
    	    if (item.equals("pepperoni") return new ChicagoStylePepperoniPizza();
        }
        
        return pizza;
    }
}

 

해결: DIP 적용

  • 추상 팩토리 메서드 패턴을 사용하여 의존성을 역전시킴

예제) PizzaFactory

더보기

추상클래스

public interface PizzaFactory {
    Pizza createPizza(String type);
}

 

서브클래스

public class NYPizzaFactory implements PizzaFactory {

	@Override
    public Pizza createPizza(String type) {
        if (type.equals("cheese")) return new NYStyleCheesePizza();
        if (type.equals("veggie")) return new NYStyleVeggiePizza();
        if (type.equals("clam")) return new NYStyleClamPizza();
        if (type.equals("pepperoni")) return new NYStylePepperoniPizza();
        return null;
    }
}