독서/헤드퍼스트 디자인패턴
[헤드퍼스트 디자인 패턴] 4장: 팩토리 패턴
yesman9
2023. 9. 13. 11:46
아래 링크 Repository 중 내가 작성한 부분만 가져온 글이다.
https://github.com/yesman9692/Head-First-Design-Patterns/tree/main
4장: 팩토리 패턴
new 연산자의 문제점
변화에 취약하다!
Duck duck;
if(picnic){
duck = new MallardDuck();
} else if(hunting){
duck = new DecoyDuck();
} else if(inBathTub){
duck = new RubberDuck();
}
위의 코드처럼 Duck이라는 인터페이스가 있지만 그럼에도 불구하고
MallardDuck, DecoyDuck, RubberDuck과 같은 인스턴스를 만들어야 한다.
코드를 변경하거나 확장시에는 기존 코드를 제거하거나 수정이 불가피하다.
피자 예제
- 기존 방식대로 아래와 같이 분기에 따라 객체를 생성해보자
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 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){
Pizza pizza = null;
if(type.equals("cheese")){
pizza = new CheesePizza();
} 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;
}
}
팩토리를 사용할 클라이언트를 만들자
public class PizzaStore{
SimplePizzaFactory factory;
public PizzaStore(SimplePizzaFactory factory){
this.factory = factory;
}
public Pizza orderPizza(String type){
Pizza pizza;
pizza = factory.CreatePizza(type);
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
}
메인문
public class Main {
public static void main(String args[]) {
SimplePizzaFactory simplePizzaFactory = new SimplePizzaFactory();
PizzaStore pizzaStore = new PizzaStore(simplePizzaFactory);
pizzaStore.orderPizza("pepperoni")
}
}
- 이제 치즈피자, 페퍼로니피자, 클램피자 각각의 피자를 생성할 때 new 키워드를 사용 할 필요가 없다간단한 팩토리를 이용해서 다양한 스타일의 피자를 만들어보자
- 뉴욕 스타일 피자
- 시카고 스타일 피자
NYPizzaFactory nyFactory = new NYPizzaFactory(); PizzaStore nyStore = new PizzaStore(nyFactory); nyStore.orderPizza("Veggie");
ChicagoPizzaFactory chicagoFactory = new ChicagoPizzaFactory();
PizzaStore chicagoStore = new PizzaStore(chicagoFactory);
chicagoStore.orderPizza("Veggie");
### 간단한 팩토리
- 디자인 패턴이라기 보다는 프로그래밍에서 자주 쓰이는 관용구에 가깝다

간단하지만, 유연성이 떨어진다. 아래 경우 매번 새로운 팩토리를 만들어야한다.
- 굽는 방식이 달라지거나
- 피자 모양이 다르거나
- 피자를 자르는 방식이 다르거나
## 팩토리 메소드
##### 피자가게(생산자) 클래스
```java
public abstract class PizzaStore{
public Pizza orderPizza(String type){
Pizza pizza;
// createPizza 메소드를 팩토리 객체에서 사용하지 않고 직접 사용한다. 단, 추상메소드를 사용한다.
pizza = createPizza(type);
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
protected abstract Pizza createPizza(String type);
}
public class NYPizzaStore extends PizzaStore{
// 추상 메소드를 서브 클래스에서 구현한다. 서브클래스를 이용해 각각 다른 피자스타일을 만들 수 있다.
Pizza createPizza(String item){
if(type.equals("cheese")){
return new NYStyleCheesePizza();
} else if(type.equals("pepperoni")){
return new NYStylePepperoniPizza();
} else if(type.equals("clam")){
return new NYStyleClamPizza();
} else if(type.equals("veggie")){
return new NYStyleVeggiePizza();
}
}
}
피자(제품) 클래스
public abstract class Pizza {
String name;
String dough;
String sauce;
List toppings = new ArrayList();
void prepare() {
System.out.println("준비 중:" + name);
System.out.println("도우 돌리는 중:" + dough);
System.out.println("소스 뿌리는 중:" + sauce);
System.out.println("토핑을 올리는 중:" + toppings);
}
void bake() {
System.out.println("굽는중");
}
void cut() {
System.out.println("자르는중");
}
void box() {
System.out.println("포장중");
}
}
// Pizza클래스를 상속받아 제품(뉴욕치즈피자)을 구현한다
public class NYStyleCheesePizza extends Pizza {
public NYStyleCheesPizz() {
name = "뉴옥 스타일 소스와 치즈 피자";
dough = "씬 크러스트 도우";
sauce = "마리나라 소스"
}
}
// Pizza클래스를 상속받아 제품(시카고치즈피자)을 구현한다
public class ChicagoStyleCheesePizza extends Pizza {
public NYStyleCheesPizz() {
name = "시카고 스타일 딥 디쉬 치즈 피자";
dough = "아주 두꺼운 크러스트 도우";
sauce = "플럼토마토 소스"
}
}
메인문
public class PizzaTestDrive {
public static void main(String[] args) {
PizzStore nyStore = new NYPizzaStore();
PizzStore chicagoStore = new ChicagoPizzaStore();
Pizza pizza = nyStore.orderPizz("cheese"); // 뉴욕 치즈피자 생성
Pizza pizza = chicagoStore.orderPizz("cheese"); // 시카고 치즈피자 생성
}
}
팩토리 메소드 패턴 살펴보기
- 객체를 생성할 때 필요한 인터페이스를 만듭니다.
- 어떤 클래스의 인스턴스를 만들지는 서브클래스에서 결정합니다.
- 팩토리 메소드 패턴을 사용하면 클래스 인스턴스 만드는 일을 서브클래스에 맡기게 됩니다.
추상 팩토리 패턴
- 그런데 같은 메뉴여도 지역마다 다른 재료를 사용한다면?
- 원재료를 생산하는 팩토리를 만들면 편하다
재료공장(생산자) 클래스
// 원재료 팩토리 인터페이스 public interface PizzaIngredientFactory { public Dough createDough(); public Sauce createSauce(); public Cheese createCheese(); public Veggies[] createVeggies(); public Pepperoni createPepperoni(); public Clams createClams(); }
// 뉴욕 피자의 재료를 생상하는 뉴욕 팩토리. 재료들은 여기서 구현한다.
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() {
Veggies veggies[] = { new Farlic(), new Onion(), new Mushroom(), new RedPepper() };
return veggies;
}
@Override
public Pepperoni createPepperoni() {
return new SlicedPepperoni();
}
@Override
public Clams createClams() {
return new Freshclams();
}
}
##### 피자(제품) 클래스
- Pizza의 prepare()를 추상 메소드화 하고, 구상 클래스에서 원재료를 팩토리에서 바로 가져와 prepare()를 구현하면, 피자마다 클래스를 지역별로 따로 만들 필요가 없다.
```java
public abstract class Pizza{
String name;
Dough dough;
Sauce sauce;
Veggies veggies[];
Cheese cheese;
Pepperoni pepperoni;
Clams clams;
abstract void prepare(); //추상 메소드로 변경됨
//기타 메소드
}
public class CheesePizza extends Pizza{
PizzaIngredientFactory ingredientFactory;
public CheesePizza(PizzaIngredientFactory ingredientFactory) {
this.ingredientFactory = ingredientFactory;
}
@Override
public void prepare() {
this.dough = ingredientFactory.createDough();
this.sauce = ingredientFactory.createSauce();
this.cheese = ingredientFactory.createCheese();
}
}
pizzaStore
public class NYPizzaStore extends PizzaStore {
protected Pizza createPizza(String item) {
Pizza pizza = null;
PizzaIngredientFactory ingredientFactory = new NYPizzaIngredientFactory();
if(item.equals("cheese")) {
pizza = new CheesePizza(ingredientFactory);
pizza.setName("뉴욕 스타일 치즈 피자")
} else if(item.equals("veggie")) {
pizza = new VeggiePizza(ingredientFactory);
pizza.setName("뉴욕 스타일 야채 피자")
} else if(item.equals("clam")) {
pizza = new ClamPizza(ingredientFactory);
pizza.setName("뉴욕 스타일 조개 피자")
} else if(item.equals("pepperoni")) {
pizza = new PepperoniPizza(ingredientFactory);
pizza.setName("뉴욕 스타일 페퍼로니 피자")
}
return pizza;
}
}
메인문
public class PizzaTestDrive {
public static void main(String[] args) {
PizzStore nyStore = new NYPizzaStore();
PizzStore chicagoStore = new ChicagoPizzaStore();
Pizza pizza = nyStore.orderPizza("cheese"); // 뉴욕 치즈피자 생성
Pizza pizza = chicagoStore.orderPizz("cheese"); // 시카고 치즈피자 생성
}
}
팩토리 메소드 패턴 vs 추상 팩토리 패턴
- 둘다 팩토리 객체를 통해 구체적인 타입을 감추고 객체 생성에 관여하는 패턴 임에는 동일하다. 또한 공장 클래스가 제품 클래스를 각각 나뉘어 느슨한 결합 구조를 구성하는 모습 역시 둘이 유사하다.
- 그러나 주의할 것은 추상 팩토리 패턴이 팩토리 메서드 패턴의 상위 호환이 아니라는 점이다. 두 패턴의 차이는 명확하기 때문에 상황에 따라 적절한 선택을 해야 한다.
- 예를들어 팩토리 메서드 패턴은 객체 생성 이후 해야 할 일의 공통점을 정의하는데 초점을 맞추는 반면, 추상 팩토리 패턴은 생성해야 할 객체 집합 군의 공통점에 초점을 맞춘다.
- 이 둘을 유사점과 차이점을 조합해서 복합 패턴을 구성하는 것도 가능하다.