Goal
- 팩토리 패턴에 대해 알아본다
- 팩토리 메소드에 대해 알아본다
- 샘플 코드를 통한 팩토리 메소드를 이해한다
팩토리 패턴(Factory Pattern)
객체 지향 디자인 패턴의 기본 원칙은 확장에 있어서는 열려 있어야 하며, 수정에 있어서는 닫혀 있어야 한다는 것이다. (OCP -개방폐쇄)
객체의 생성을 담당하는 클래스를 한 곳에서 관리하여 결합도를 줄이기 위하여 팩토리 패턴이 출현했다.
팩토리 메소드 패턴(Factory Method Pattern)
팩토리 메소드 패턴에서는 객체를 생성할 때 필요한 인터페이스를 만든다. 어떤 클래스의 인스턴스를 만들지는 서브클래스에서 결정함으로써 객체 생성을 캡슐화하는 패턴이다.
즉, 어떤 클래스의 인스턴스를 만들지를 결정해서가 아니라, 생산자 클래스가 실제 생산될 제품을 전혀 모르는 상태로 만들어지기 때문이다. 정확히 말하면, 사용하는 서브클래스에 따라 생성되는 객체 인스턴스가 결정된다.
왜 or 언제 전략 패턴을 사용하는가?
SimpleFactory는 새로운 클래스가 추가되었을 때 Factory 클래스를 수정해야 한다는 한계가 있었으나, 기존 코드의 변경 없이 확장하기 위해서 사용한다.
팩토리 메소드 패턴 사용 시 장점
- 객체간의 결합도를 낮출 수 있다
- 인스턴스 생성을 서브클래스에 위임하여 분리되어 있다 => SRP 단일책임의 원칙
- 수정에는 닫혀있고 확장에는 열려있다=> OCP 개방폐쇄의 원칙
단, 간단한 기능을 사용할 때보다 많은 클래스를 정의해야 하기 때문에 코드량이 증가하는 단점이 있다.
to-be 피자 가게 프레임워크 만들기
팩토리 메소드 선언하기
public abstract class PizzaStore {
public Pizza orderPizza(String type) {
Pizza pizza = createPizza(type);
System.out.println("--- Making a " + pizza.getName() + " ---");
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
abstract Pizza createPizza(String item);
}
피자를 만드는 일 자체는 전부 PizzaStore 클래스에 진행하면서도 지점의 스타일을 살릴 수 있는 방법
- createPizza() 메소드를 PizzaStore에 넣고, 추상 메소드로 선언 - (팩토리 메소드)
- abstract를 사용하여 서브클래스가 객체 생성을 책임지도록
- Pizza 팩토리 메소드는 특정 객체를 리턴하며, 그 객체는 보통 슈퍼클래스에서 정의한 메소드 내에 쓰임
- (String item) 팩토리 메소드를 만들 때 매개변수로 만들 객체 종류를 선택 가능
- Pizza 인스턴스를 만드는 일은 팩토리 메소드에서 처리
서브 클래스
- 각 서브클래스는 createPizza() 메소드를 오버라이드하지만, orderPizza() 메소드는 PizzaStore에서 정의한 내용 그대로 사용.
- 우리가 정의한 메소드를 변경 불가하게 만들고 싶다면 final로 선언하면 됨
createPizza()
- createPizza()는 추상 메소드로 선언되어 있기 때문에 서브 클래스에서 반드시 구현해야함
orderPizza()
- orderPizza()는 서브 클래스가 아닌 PizzaStore 추상 클래스에서 정의하므로, 이 메소드는 어떤 서브(구상) 클래스에서 코드를 실행하고 피자를 만드는지 알 수 없음
- PizzaStore와 Pizza는 서로 완전히 분리되어 있음
- orderPizza()에서 피자 객체를 받을 때는 createPizza()를 호출한다. 하지만 어떤 종류의 피자를 받는지 알 수 없다
- Pizza의 서브 클래스가 호출받아 피자를 만들고, orderPizza()가 결정하지 않기 때문
- 피자의 종류는 어떤 서브클래스를 선택했는냐에 따라 결정됨
피자 스타일 서브 클래스 만들기
public class NYPizzaStore extends PizzaStore {
Pizza createPizza(String item) {
if (item.equals("cheese")) {
return new NYStyleCheesePizza();
} else if (item.equals("veggie")) {
return new NYStyleVeggiePizza();
} else if (item.equals("clam")) {
return new NYStyleClamPizza();
} else if (item.equals("peperoni")) {
return new NYStylePepperoniPizza();
} else return null;
}
}
- createPizza()는 Pizza 객체를 리턴하며, Pizza의 서브클래스 가운데 어느 구상 클래스 객체의 인스턴스를 만들어 리턴할지는 전적으로 PizzaStore의 서브클래스에 의해 결정된다.
- NYPizzaStore는 PizzaStore를 확장하기에 orderPizza() 메소드를 자동으로 상속받음
- createPizza()는 PizzaStore에서 추상메소드로 선언되어 있기에 무조건 구현해야함
- 슈퍼클래스(PizzaStore)에 있는 orderPizza() 메소드는 어떤 피자가 만들어지는지 알 수 없으며, 피자를 준비하고 굽고 자르고 포장하는 작업을 처리할 뿐이다.
피자가 만들어지는 과정
- 피자 가게 생성
- PizzaStore nyPizzaStore = new NYPizzaStore();
- nyPizzaStore 인스턴스 생성
- 피자 주문
- nyPizzaStore.orderPizza("cheese");
- nyPizzaStore 인스턴스의 orderPizza()메소드가 호출
- 피자 생성
- Pizza pizza = createPizza("cheese");
- 팩토리 메소드인 createPizza() 메소드는 서브클래스에서 구현
- 피자 만들기 작업 마무리
- orderPizza()메소드에서 피자 객체를 받았으며, 그 피자 객체가 어느 구상 클래스의 객체인지 알지 못함
- 이 메소드들은 모두 createPizza()팩토리 메소드에서 리턴한 특정 피자 객체내에 정의 되어 있음
- pizza.prepare();
- pizza.bake();
- pizza.cut();
- pizza.box();
- 그리고 createPizza()메소드는 NYPizzaStore에 정의 되어 있음
Pizza 클래스 만들기
public abstract class Pizza { // Pizza 추상 클래스를 만들어 이를 통해 구상 클래스를 만든다
// 피자마다 필요한 이름, 반죽, 소스
String name;
String dough;
String sauce;
ArrayList<String> toppings = new ArrayList<String>();
// 피자 준비 과정
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;
}
public String toString() {
StringBuffer display = new StringBuffer();
display.append("---- " + name + " ----\n");
display.append(dough + "\n");
display.append(sauce + "\n");
for (String topping : toppings) {
display.append(topping + "\n");
}
return display.toString();
}
}
구상 서브 클래스
NYStyleCheesePizza 뉴욕 스타일 치즈 피자
public class NYStyleCheesePizza extends Pizza {
public NYStyleCheesePizza() {
name = "뉴욕 스타일 소스와 치즈 피자";
dough = "씬 크러스트 도우";
sauce = "마리나라 소스";
toppings.add("잘게 썬 레지아노 치즈");
}
}
ChicagoStylePepperoniPizza 시카고 스타일 페페로니 피자
public class ChicagoStylePepperoniPizza extends Pizza {
public ChicagoStylePepperoniPizza() {
name = "시카고 스타일 페페로니 피자";
dough = "아주 두꺼운 크러스트 도우";
sauce = "플럼토마토 소스";
toppings.add("잘게 조각낸 모짜렐라 치즈");
toppings.add("블랙 올리브");
toppings.add("시금치");
toppings.add("가지");
toppings.add("슬라이스 페페로니");
}
// 시카고 스타일 피자는 cut()메소드르 오버라이딩하여 커스텀한다
void cut() {
System.out.println("네모난 모양으로 피자 자르기");
}
}
클라이언트 코드
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("rosie가 주문 한" + pizza.getName() + "\n");
pizza = chicagoStore.orderPizza("peperoni");
System.out.println("posie가 주문한 " + pizza.getName() + "\n");
}
}
결과
--- Making a 뉴욕 스타일 소스와 치즈 피자 ---
준비중 뉴욕 스타일 소스와 치즈 피자
도우를 돌리는 중...
소스를 뿌리는 중...
토핑을 올리는 중:
잘게 썬 레지아노 치즈
175도에서 25분 간 굽기
피자를 사선으로 자르기
상자에 피자 담기
rosie가 주문 한뉴욕 스타일 소스와 치즈 피자
--- Making a 시카고 스타일 딥 디쉬 치즈 피자 ---
준비중 시카고 스타일 딥 디쉬 치즈 피자
도우를 돌리는 중...
소스를 뿌리는 중...
토핑을 올리는 중:
잘게 조각낸 모짜렐라 치즈
175도에서 25분 간 굽기
네모난 모양으로 피자 자르기
상자에 피자 담기
posie가 주문한 시카고 스타일 딥 디쉬 치즈 피자
다이어그램
생산자(Creator) 클래스
- 추상 생산자 클래스 PizzaStore
- 나중에 서브클래스에서 제품(객체)를 생산하려고 구현하는 팩토리메소드(추상 케소드)를 정의
- 생산자 자체는 어떤 구상 제품 클래스가 만들어질지 미리 알 수 없음
- 제품을 생산하는 구상 생산자 클래스 concrete creator
- NYPizzaStore, ChicagoPizzaStore
- NYPizzaStore에는 뉴욕 스타일 피자 생성 방법이 캡슐화, ChicagoPizzaStore에는 시카고 스타일 피자 생성 방법이 캡슐화 되어있음
- 팩토리 메소드 createPizza()
- 제품(객체) 생산
- 각 분점마다 PizzaStore의 서브 클래스가 따로 있으므로, createPizza()메소드 구현을 활용하여 그 가게 고유 피자 생산 가능
제품(Product) 클래스
- 팩토리 Pizza
- 제품을 생산하며 PizzaStore는 Pizza를 만든다
- 구상 클래스
- NYStyleCheesePizza, NYStylePeperoniPizza, ChicagoStyleCheesePizza ...
- 피자 가게에서 만들어지는 피자들
정리
모든 팩토리 패턴은 객체 생성을 캡슐화한다. 팩토리 메소드 패턴은 서브클래스에서 어떤 클래스를 만들지 결정함으로써 객체 생성을 캡슐화한다.
참고
헤드퍼스트 디자인 패턴
https://velog.io/@ellyheetov/Factory-Pattern
'JAVA > 디자인 패턴' 카테고리의 다른 글
[Factory Pattern] 팩토리 패턴 - 추상 팩토리 (0) | 2023.07.10 |
---|---|
[Simple Factory] 간단한 팩토리 (0) | 2023.07.07 |
[Strategy Pattern] 전략 패턴 (1) | 2023.07.06 |
[Decorator Pattern] 데코레이터 패턴 (0) | 2023.07.04 |
[Proxy Pattern] 프록시 패턴 - 보호 프록시 (0) | 2023.07.04 |