[디자인 패턴] 팩토리 패턴(Factory Pattern) By starseat 2023-08-14 14:43:44 알고리즘 Post Tags # 팩토리 - 객체 생성 처리 - 디자인 패턴이라기 보다는 프로그래밍에 자주 쓰이는 관용구에 가깝다. ## 팩토리 예제 - `PIzza` 라는 부모 클래스가 있고, 피자의 종류에 따라 `CheesePizza`, `GreekPizza`, `PepperoniPizza` 등 여러 피자가 존재 한다고 가정. - 아래 예제는 피자를 주문받아서 처리하는 과정을 나타냄. ```java Pizza orderPizza(String type) { Pizza pizza; // 객제 생성을 담당하는 Factory // 인스턴스를 만드는 구상 클래스를 선택하는 부분 if (type.equals("cheese")) { pizza = new CheesePizza(); } else if (type.equals("greek") { pizza = new GreekPizza(); } else if (type.equals("pepperoni") { pizza = new PepperoniPizza(); } else if (type.equals("Clam") { pizza = new ClamPizza(); } else if (type.equals("veggie") { pizza = new VeggiePizza(); } pizza.prepare(); pizza.bake(); pizza.cut(); pizza.box(); return pizza; } ``` ### 팩토리 예제 2 - 위 예제에서 피자를 준비하고 굽고, 자르고, 포장하는 작업은 공통이므로 피자를 생성하는 부분만 따로 처리한다. - 이 처리는 `SimplePizzaFactory` 라는 서브 클래스를 생성하여 처리한다. ```java // 피자 생성을 담당하는 SimplePizzaFactory public class SimplePizzaFactory { public Pizza createPizza(String type) { Pizza pizza = null; if (type.equals("cheese")) { pizza = new CheesePizza(); } else if (type.equals("greek") { pizza = new GreekPizza(); } else if (type.equals("pepperoni") { pizza = new PepperoniPizza(); } else if (type.equals("Clam") { pizza = new ClamPizza(); } else if (type.equals("veggie") { pizza = new VeggiePizza(); } return pizza; } } // 피자 매장에서 orderPizza 를 수정한다. public class PizzaStore { SimplePizzaFactory factory; // SimplePizzaFactory 가 PizzaStore 생성자에 팩토리 객체가 전달된다. public PizzaStore(SimplePizzaFactory factory) { this.factory = factory; } public Pizza orderPizza(String type) { // 피자 생성을 팩토리로 변경하였다. // 이제 더 이상 구상 클래스의 인스턴스를 만들 필요가 없어졌다. Pizza pizza = factory.createPizza(type); pizza.prepare(); pizza.bake(); pizza.cut(); pizza.box(); return pizza; } } ``` ## 추가 - new 에 대해서... - `new` 연산자는 `구상` 이라는 용어를 떠올리자. - `new` 를 사용하면 쿠상 클래스의 인스턴스가 만들어진다. - 팩토리를 사용하면 인스턴스의 형식은 실행 시에 주어진 조건에 따라 결정된다. - 인터페이스에 맞춰서 코딩하면 시스템에서 일어날 수 있는 여러 변화에 대응할 수 있다. - 인터페이스를 바탕으로 만들어진 코드는 어떤 클래스든 특정 인터페이스만 구현하면 사용할 수 있다. (**다형성**) # 팩토리 메소드 패턴(Factory Method Pattern) - 객체를 생성할 때 필요한 인터페이스를 만든다. - 어떤 클래스의 인스턴스를 만들지는 서브 클래스에서 결정한다. - 팩토리 메소드 패턴을 사용하며 클래스 인스턴스 만드는 일을 서브 클래스에게 맡기게 된다. ## 팩토리 메소드 패턴 구조 ![image.png](/uploads/_temp/20230814/c8dd814b00c7812bdafedaa7642e6ea7.png) - **Creator** 추상 클래스에서 객체를 만드는 메소드, 즉 팩토리 메소드용 인터페이스를 제공한다. - **Creator** 추상 클래스에 구현되어 있는 다른 메소드는 팩토리 메소드에 의해 생상된 제품으로 필요한 작업을 처리한다. - 하지만 실제 팩토리 메소드를 구현하고 제품(객체 인스턴스)을 만드는 일은 서브 클래스 에서만 할 수 있다. - **사용하는 서브클래스에 따라 생상되는 객체 인스턴스가 결정된다.** ## 팩토리 메소드 패턴 예제(구현) - 위의 `팩토리 예제 2` 에서 피자 생성을 추상 클래스로 변경한다. - 이러면 피자를 만드는 일 자체는 전부 PizzaStore 클래스에서 진행하면서도 각 지점의 스타일을 살릴 수 있다. ### 피자 매장 - **PisszStore** ```java public class PizzaStore { // 피자 생성을 담당하던 SimplePizzaFactory 삭제 public Pizza orderPizza(String type) { // 팩토리가 아닌 추상 메소드 사용 Pizza pizza = createPizza(type); pizza.prepare(); pizza.bake(); pizza.cut(); pizza.box(); return pizza; } // 피자 생성을 담당하는 createPizza() 메소드를 추상클래스로 선언. // 이제 서브 클래스에서 피자 특징에 따라 피자를 생성할 것이다. protected abstract Pizza createPizza(String type); } ``` - **NYPizzaStore** ```java public class NYPizzaStore extends PizzaStore { protected Pizza createPizza(String type) { Pizza pizza = null; if (type.equals("cheese")) { pizza = new NYStyleCheesePizza("치즈 피자"); } else if (type.equals("greek") { pizza = new NYStyleGreekPizza("그리스 피자"); } else if (type.equals("pepperoni") { pizza = new NYStylePepperoniPizza("페퍼로니 피자"); } else if (type.equals("Clam") { pizza = new NYStyleClamPizza("조개 피자"); } else if (type.equals("veggie") { pizza = new NYStyleVeggiePizza("야채 피자"); } else { System.out.println("오류: 알 수 없는 피자 종류"); } return pizza; } } ``` - **ChicagoPizzaStore** ```java public class ChicagoPizzaStore extends PizzaStore { protected Pizza createPizza(String type) { Pizza pizza = null; if (type.equals("cheese")) { pizza = new ChicagoStyleCheesePizza("치즈 피자"); } else if (type.equals("greek") { pizza = new ChicagoStyleGreekPizza("그리스 피자"); } else if (type.equals("pepperoni") { pizza = new ChicagoStylePepperoniPizza("페퍼로니 피자"); } else if (type.equals("Clam") { pizza = new ChicagoStyleClamPizza("조개 피자"); } else if (type.equals("veggie") { pizza = new ChicagoStyleVeggiePizza("야채 피자"); } else { System.out.println("오류: 알 수 없는 피자 종류"); } return pizza; } } ``` ### 피자 - **Pizza** ```java public abstract class Pizza { String name; String dough; String sauce; List toppings = new ArrayList<>(); void prepare() { System.out.println("준비 중: " + name); System.out.println("도우를 돌리는 중..."); System.out.println("소스를 뿌리는 중...."); System.out.println("토핑을 올리는 중....."); for(String topping : toppings) { System.out.println(" " + topping); } } void bake() { System.out.println("175 도 에서 25 분 간 굽기"); } void cut() { System.out.println("피자를 사선으로 자르기"); } void box() { System.out.println("상자에 피자 담기"); } public String getName() { return name; } } ``` - **NYStyleCheesePizza** ```java public class NYStyleCheesePizza extends Pizza { public NYStyleCheesePizza(String name) { name = "뉴욕 스타일 " + name; dough = "씬 크러스트 도우"; sauce = "마리나라 소스"; toppings.add("잘게 썬 레지아노 치즈"); } } ``` - **ChicagoStyleCheesePizza** ```java public class ChicagoStyleCheesePizza extends Pizza { public ChicagoStyleCheesePizza(String name) { name = "시카고 스타일 딥 디쉬 " + name; dough = "아주 두꺼운 크러스트 도우"; sauce = "플럼토마토 소스"; toppings.add("잘게 조각낸 모짜렐라 치즈"); } // 사카고 스타일로 cut() 메소드 오버라이드 void cut() { System.out.println("네모난 모양으로 피자 자르기"); } } ``` - 기타 다른 피자는 중복된 소스가 많으므로 생략.. ### 테스트 ```java public class PizzaTest { public static void main(String[] args) { PizzaStore nyStore = new NYPizzaStore(); PizzaStore chicagoStore = new ChicagoPizzaStore(); Pizza nyPizza = nyStore.orderPizza("cheese"); System.out.println("에단(Edan)이 주문한 " + nyPizza.getName() + "\n"); Pizza chicagoPizza = chicagoStore.orderPizza("cheese"); System.out.println("조엘(Joel)이 주문한 " + chicagoPizza.getName() + "\n"); } } ``` # 디자인 원칙 ## 디자인 원칙 6 - **의존성 뒤집기 원칙(Dependency Inversion Principle)** - 추상화된 것에 의존하게 만들고 - 구상 클래스에 의존하지 않게 만든다. 앞의 [전략 패턴(Strategy Pattern)](https://starseat.net/blog/view/189#top) 에서 언급한. *"구현보다는 인터페이스에 맞춰서 프로그래밍 하기"* 와 비슷하지만 의존성 뒤집기 원칙에서는 추상화를 더 많이 강조한다. 이 원칙에서는 고수준 구성 요소가 저수준 구성 요소에 의존하면 안되며, 항상 추상화에 의존하게 만들어야 한다는 뜻이 담겨 있다. - 앞 예제에서 `PizzaStore` 는 **고수준 구성 요소**라고 할 수 있고, `Pizza` 클래스는 **저수준 구성 요소** 라고 할 수 있다. '**고수준 구성 요소**'는 다른 '**저수준 구성 요소**'에 의해 정의되는 행동이 들어있는 구성 요소를 뜻한다. - `PizzaStore`의 행동은 피자에 의해 정의되므로 `PizzaStore`는 **고수준 구성 요소** 라고 할 수 있다. - `PizzaStore`는 다양한 피자 객체를 만들고, 피자를 준비하고, 굽고, 자르고 포장하는데 이 때 `PizzaStore` 에서 사용하는 `피자 객체`는 **저수준 구성 요소** 이다. ### 의존성 뒤집기 원칙을 지키는 방법 - 변수에 구상 클래스의 레퍼런스를 저장하지 말자. - `new 연산자` 를 사용하면 구상 클래스의 레퍼런스를 사용하게 된다. - 그러니 팩토리를 써서 구상 클래스의 레퍼런스를 변수에 저장하는 일을 미리 방지 하자. - 구상 클래스에서 유도된 클래스를 만들지 말자. - 구상 클래스에서 유도된 클래스를 만들면 특정 구상 클래스에 의존하게 된다. - 인터페이스나 추상 클래스처럼 추상화된 것으로부터 클래스를 만들어야 한다. - 베이스 클래스에 이미 구현되어 있는 메소드를 오버라이드 하지 말자. - 이미 구현되어 있는 메소드를 모버라이드 한다면 베이스 클래스가 제대로 추상화 되지 않는다. - 베이스 클래스에서 메소드를 정의할때는 모든 서브 클래스에서 공유할 수 있는 것만 정의해야 한다. **이 방법(가이드라인)은 항상 지켜야 하는 규칙이 아니라, 지향해야 하는 바를 알려줄 뿐!** # 추가 책에선 **추상 팩토리 패턴(Abstract Factory Pattern)** 에 대해서도 소개하고 있는데 내용이 복잡해져서 나중에 따로 정리할 예정이다. # 참조 - [헤드 퍼스트 디자인 패턴](https://product.kyobobook.co.kr/detail/S000001810483) Previous Post [디자인 패턴] 데코레이터 패턴(Decorator Pattern) Next Post [디자인 패턴] 싱글턴 패턴(Singleton Pattern)