티스토리 뷰

반응형

팩토리 메소드 패턴(Factory Method Pattern)

 부모 클래스가 객체를 생성할 때 필요한 인터페이스를 제공하지만, 실제로 어떤 클래스의 인스턴스를 생성할지는 자식 클래스가 결정하는 패턴을 말합니다.
→ 사용하는 자식 클래스에 따라 생성하는 인스턴스 객체를 변경할 수 있습니다.
 

Q) 실제 객체를 생성하는 자식 클래스가 하나밖에 없는데도 굳이 팩토리 메소드 패턴을 써야할까요?

 하나밖에 없더라도 제품을 생산하는 코드와 이미 만들어진 제품을 사용하는 코드를 분리할 수 있기 때문에 팩토리 메소드 패턴은 충분히 유용하다고 합니다 :)
 
 

구조 및 예시

 
 팩토리 메소드 패턴을 사용해 책을 따라 피자 객체를 생성하는 간단한 클래스를 만들어봅시다.

 PizzaStore는 추상 클래스입니다. 서브클래스에서 피자 객체를 생산할 때 사용될 추상 팩토리 메소드 createPizza()를 정의하고 있습니다. PizzaStore 클래스는 createPizza() 메소드를 갖고 있지만 실제로 이 함수를 통해 어떤 객체가 만들어지는지는 모릅니다.
 

public abstract class PizzaStore {
    public Pizza orderPizza(String type) {
        Pizza pizza;
        
        pizza = createPizza(type);
        pizza.prepare();
        pizza.bake();
        pizza.cut();
        pizza.box();
        
        return pizza;
    }
    
    // Pizza 인스턴스를 만드는 일은 팩토리 메소드에서 맡아 처리한다.
    // 슈퍼클래스의 클라이언트 코드와 서브클래스의 객체 생성 코드를 분리할 수 있다.
    abstract Pizza createPizza(String type);
}

 
 
이제 PizzaStore 클래스를 상속해 뉴욕 스타일 피자 클래스를 만들어보겠습니다. 아래 코드를 변형하면 시카고 스타일, 캘리포니아 스타일 등 또 다른 스타일의 PizzaStore 서브클래스를 만들 수 있습니다.
 

public class NYPizzaStore extends PizzaStore {

    Pizza createPizza(String item) {
    	if (item.equals("cheese")) {
        	return new NYStyleCheesePiza();
        } else if (tem.equals("veggie")) {
        	return new NYStyleVeggiePizza();
        } else if (tem.equals("clam")) {
        	return new NYStyleClamPizza();
        } else if (tem.equals("pepperoni")) {
        	return new NYStylePepperoniPizza();
        } else return null;            
    }
}

 
  PizzaStore처럼 제품을 생산하는 클래스를 구상 생산자(concrete creator)라고 부릅니다. createPizza() 메소드가 제품을 생산하는 팩토리 메소드가 됩니다. createPizza()는 들어오는 파라미터에 따라 서로 다른 Pizza 서브클래스 객체를 만들 수 있다.
 
 

사용방법

public class PizzaTestDrive {

    public static void main(String[] args) {
        PizzaStore nyStore = new NYPizzaStore();
        PizzaStore chicagoStore = new ChicagoPizzaStore();
        
        Pizza pizza = nyStore.orderPizza("cheese");
        System.out.println("에단이 주문한 " + pizza.getName() + "\n");
        
        Pizza pizza = chicagoStore.orderPizza("cheese");
        System.out.println("조엘이 주문한 " + pizza.getName() + "\n");
    }
}

 
 


 

의존성 뒤집기 원칙(Dependency Inversion Principle)

DIP는 객체지향설계 5원칙 중 하나로 아래와 같은 원칙을 말합니다.

추상화된 것에 의존하게 만들고 구현 클래스에 의존하지 않게 만든다.

책에 따르면 고수준의 구성 요소가 저수준의 구성 요소에 의존하면 안 되며, 항상 추상화에 의존하게 만들어야 합니다.

 
 PizzaStore 예제에 사용된 팩토리 메소드 패턴에도 DIP 원칙이 적용되어 있습니다!

  • PizzaStore는 NYStyleCheesPizza, NYStylePepperoniPizza와 같은 구현 클래스에 의존적이지 않습니다.
  • PIzzaStore는 Pizza라는 하나의 추상 클래스에만 의존하고 있습니다.


이렇듯 팩토리 메소드 패턴을 적용하면 고수준 구성 요소인 PizzaStore와 저수준 구성 요소인 피자 객체 모두가 추상 클래스인 Pizza에 의존합니다.
 
 

DIP 원칙을 지키는 방법

  1. 변수에 구현 클래스의 레퍼런스를 저장하지 않습니다. (new 연산자 사용 X)
  2. 구현 클래스에서 다시 유도되는 클래스를 만들지 않습니다.
  3. 추상 클래스에 이미 구현되어 있는 메소드를 오버라이드하지 않습니다.

 3번 추가 설명) 추상 클래스에서 메소드를 정의할 때는 모든 서브클래스에 공유될 수 있는 것만 정의합니다!
 
 
 


추상 팩토리 패턴(Abstract Factory Pattern)

 추상 팩토리 패턴은 클라이언트가 구현된 클래스에 의존하지 않고 인터페이스를 사용해 관련된 제품들의 모음을 생산하는 패턴입니다. 이 때 구현 클래스(제품)은 팩토리 인터페이스의 서브클래스를 통해서 만들어집니다. 비즈니스 코드와 제품을 생산하는 팩토리 코드를 분리할 수 있어 지역이나 운영체제 등이 서로 다른 상황에서 적합한 제품을 생산할 수 있습니다.
 

팩토리 메소드 패턴 ↔️ 추상 팩토리 패턴

공통점

  • 객체 생성을 캡슐화하는 패턴입니다.
  • 클라이언트와 구현 클래스를 분리해 유연한 코드를 작성할 수 있습니다.


차이점

  • 팩토리 메소드 패턴: 상속을 통해 팩토리 코드를 분리합니다.
  • 추상 팩토리 패턴: 구성(composition)을 통해 팩토리 코드를 분리합니다.

 
 
 위에서 사용한 PizzaStore 예제로 추상 팩토리 패턴을 다시 알아봅시다. 피자가게의 모든 지점은 같은 피자 메뉴를 판매하고 있습니다. 그러나 지점마다 피자에 사용하는 원재료는 다릅니다. (지점 별로 지역 특산물을 사용한다고 해요!) 지점에 따라 원재료를 관리하는 코드를 작성해봅시다.
 

원재료를 생산하는 팩토리용 인터페이스 PizzaIngredientFactory

public interface PizzaIngredientFactory {

    public Dough createDough();
    public Sauce createSauce();
    public Cheese createCheese();
    public Veggies[] createVeggies();
    public Pepperoni createPepperoni();
}

 

뉴욕의  원재료 팩토리 만들기

public class NYPizzaIngredientFactory implements PizzaIngredientFactory {

    public Dough createDough() {
        return new ThinCrustDough();
    }
    
    public Sauce createSauce() {
        return new MarinaraSauce();
    }
    
    ...
}

팩토리 코드를 변형해 시카고, 캘리포니아의 원재료 팩토리도 만들수 있겠죠?


피자 클래스 수정하기

public abstract class Pizza {
    String name;
    
    Dough dough;
    Sauce sauce;
    Cheese cheese;
    Veggies veggies[];
    ...
    
    // 피자를 만드는 데 필요한 재료를 원재료 팩토리에서 가져오는 추상 메소드
    abstract void prepare();
    
    void bake() {
        ...
    }
    
    void cut() {
        ...
    }
    
    ...
}

 

피자 클래스를 상속해 치즈 피자 만들기

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

 
 
 
참조:

 

헤드 퍼스트 디자인 패턴(개정판)

유지관리가 편리한 객체지향 소프트웨어 만들기! “『헤드 퍼스트 디자인 패턴(개정판)』 한 권이면 충분합니다!”

m.hanbit.co.kr

 

반응형