-
Factory PatternJava/디자인패턴 2024. 1. 17. 11:10
🤔 인터페이스를 사용한다고 해도 구상 클래스의 인스턴스를 만들어야 하지 않나?
Duck duck = new MallardDuck(); // Duck이라는 인터페이스를 써서 유연하게 만들려고 해도, 그럼에도 구상 클래스의 인스턴스를 만들어야 한다. Duck duck; if (picnic) { duck = new MallardDuck(); } else if (hunting) { duck = new DecoyDuck(); } else if (inBathTub) { duck = new RubberDuck(); }
- 이 코드를 보면 구상 클래스의 인스턴스가 여러 개 있고, 그 인스턴스의 형식은 실행 시 조건에 따라 결정된다.
- 변경하거나 확장할 때, 코드를 다시 확인하고 새롭게 추가하거나 기존 코드 제거
=> 관리와 갱신이 어렵다.
=> 인터페이스에 맞춰서 코딩하면 시스템에서 일어날 수 있는 여러 변화에 대응 가능 (인터페이스만 구현하면 사용 가능, 다형성)
=> 구상 클래스를 많이 사용하면 새로운 구상 클래스 추가될 때마다 코드 고쳐야 함
=> 새로운 구상 형식을 써서 확장할 때에는 어떻게 해서든 다시 열 수 있게 만들어야 한다...!
Pizza orderPizza(String type) { Pizza pizza; // 이 부분이 피자가 바뀔 때마다 코드를 계속 추가하고 삭제해야 하는 것이 문제이다. // ( 인스턴스를 만드는 구상 클래스 선택하는 부분 ) // 이 부분을 따로 빼두자(팩토리) if (type.equals("cheese")) { pizza = new CheezePizza(); } 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; }
인스턴스를 만드는 구상 클래스를 선택하는 부분 -> 이 부분을 따로 빼내자(피자 종류에 따라 계속 바뀌는 부분이므로)public class SimplePizzaFactory { public Pizza createPizza(String type) { if (type.equals("cheese")) { pizza = new CheezePizza(); } 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; } }
- 피자 객체 생성 작업을 팩토리 클래스로 캡슐화 해놓으면 구현을 변경할 때 여기저기 고칠 필요 없이 팩토리 클래스 하나만 고치면 된다.
- 피자 객체를 받아서 주문하는 클래스 말고도, 피자를 설명하는 클래스, 가격을 알려주는 클래스 등 다른 방식으로 피자를 처리하는 클래스에도 이 팩토리를 사용할 수 있다.
🤔 간단한 팩토리?
- 디자인 패턴이라기보다는, 프로그래밍에서 자주 쓰이는 관용구에 가까움
- PizzaStore(팩토리 사용하는 클라이언트)
- SimplePizzaFactory (객체를 생성하는 팩토리, 구상 Pizza 클래스를 직접 참조)
- Pizza (팩토리에서 만드는 피자)
- CheesePizza, VeggiePizza, ClamPizza, PepperoniPizza 등등
- 팩토리에서 생산하는 제품에 해당하는 구상 클래스
- Pizza 인터페이스를 구현하며, 각각은 구상 클래스이다.
public abstract class PizzaStore { public Pizza orderPizza(String type) { Pizza pizza; pizza = createPizza(type); pizza.prepare(); pizza.bake(); pizza.cut(); pizza.box(); return pizza; } abstract Pizza createPizza(String type); }
- createPizza : PizzaStore 상속받은 NYStylePizzaStore, ChicagoStylePizzaStore 2가지
- ex ) NYStylePizzaStore의 createPizza(type)
- NYStyleCheesePizza()
- NYStylePepperoniPizza()
- ex ) NYStylePizzaStore의 createPizza(type)
- orderPizza : 그냥 구현하지 않고 가져다 사용, 만약 못 고치도록 막고 싶다면 final 붙이면 된다
🤔 팩토리 메서드
- 객체를 생성할 때 필요한 인터페이스를 만든다.
- 어떤 클래스의 인스턴스를 만들 지는 서브 클래스에서 결정한다.
- 즉, 클래스 인스턴스 만드는 일을 서브 클래스에게 맡기게 된다.
🍕 Creator- 추상 클래스, 팩토리 메소드 용 인터페이스를 제공한다.
- 인터페이스만 제공, 실제 팩토리 메소드를 구현하고 객체 인스턴스 만드는 일은 서브 클래스에서만 할 수 있음
- PizzaStore 추상클래스 상속받는 NyPizzaStore, ChicagoPizzStore
- PizzaStore의 orderPizza : pizza = createPizzas(type) 이렇게 호출
- 사용하는 쪽에서 어느 PizzaStore의 orderPizza호출하냐 따라 NyPizza가 만들어질수도, ChicagoPizza가 만들어질수도 있다!
- 즉, 서브 클래스에서 어떤 객체가 생성될지 결정되는 것 !
🤔 간단한 팩토리와 팩토리 메서드 비교
🤔 추상 팩토리
구상 클래스에 의존하지 않고도 서로 연관되거나 의존적인 객체로 이루어진 제품군을 생산하는 인터페이스를 제공하고, 구상 클래스는 서브클래스에서 만들어진다.
- 위의 시나리오에서 원재료들을 생산하는 팩토리도 만들자!
public interface PizzaIngredientFactory { public Dough createDough(); public Sauce creaetSauce(); public Cheese createCheese(); public Veggies[] createVeggies(); public Pepperoni createPepperoni(); public Clams createClam(); }
- 각 스타일의 피자별로 implements PizzaIngredientFactory 해서 사용
- 각각 createXXX 메소드를 지점별로 알맞게 만든다.
- ex ) 냉동 조개를 쓸 수도 있고, 생 조개 쓸 수도 있고..!
- 기존 PizzaClass에서도 prepare()을 추상 메소드로 만든다.
EX) 🧀🧀 CheesePizza
public class CheesePizza extends Pizza { PizzaIngredientFactory ingredientFactory; public CheesePizza(PizzaIngredientFactory ingredientFactory) { this.ingredientFactory = ingredientFactory; } void prepare() { dough = ingredientFactory.createDough(); sauce = ingredientFactory.createSauce(); // 등등 재료가 필요할 때마다 팩토리에 있는 메소드 호출하여 만든다 } }
EX) 🧀🧀 NYPizzaStorepublic class NYPizzaStore extends PizzaStore { protected Pizza createPizza(String item){ Pizza pizza = null; PizzaIntgredientFactory ingredientFactory = new NYPizzaIngredientFactory(); // .. 중략, item별로 치즈 피자 만들기 등등 수행 } }
🤔 팩토리 메소드 패턴(PizzaStore)과 추상 팩토리 패턴(PizzaIngredientFactory) 비교
🍎 팩토리 메소드 패턴 🍎 🍏 추상 팩토리 패턴 🍏 어플리케이션을 특정 구현으로부터 분리하기 위해 사용 🍎 클래스를 사용해서 만든다.
- 상속을 통해 객체를 만든다.
- 클래스를 확장, 팩토리 메소드 오버라이드
🍎 PizzaStore ~ createPizza() 라는 팩토리 메소드 override
🍎 NYPizzaStore, ChicagoPizzaStore (상속)🍏 객체 구성을 통해 객체를 만든다.
- 필드로 클래스 인스턴스 참조
- 제품군을 만드는 추상 형식 제공
- 제품이 생산되는 방법은 이 형식의 서브 클래스에서 정의
🍏 PizzaIngredientFactory
- createDough, createCheese 등 추상 형식을 제공
- 구체적으로 어떤 dough, cheese인지는 Ny, Chic(하위)에서
🍏 NyPizzaStore의 createPizza()
PizzaIngredientFactory ingredientFactory = new NyPizzaIngredientFactory 넣어주기🍎 클라이언트 코드와 인스턴스를 만들어야 할 구상 클래스를 분리 🍏 새로운 제품을 추가 시 인터페이스 변경이 일어난다.
ex) 현재는 dough, cheese인데 ham 이런거 추가한다면?🍎 클라이언트는 자신이 사용할 추상 형식만 알고 있으면 OK
서브 클래스에서 구상 형식을 처리 해준다.🍏 팩토리를 사용하려면, 팩토리 인스턴스를 만들고, 추상 형식을 써서 만든 코드에 전달 🍎 어떤 구상 클래스가 필요할지 미리 알 수 없을 때에도 유용 🍏 클라이언트에서 서로 연관된 일련의 제품을 만들 때 즉, 제품 군을 만들 때 활용 'Java > 디자인패턴' 카테고리의 다른 글
Decorator Pattern (0) 2024.01.11 Observer Pattern (0) 2024.01.04