추상 팩토리 패턴(Abstract Facgtory Pattern)은 구상 클래스에 의존하지 않고 서로 연관되거나 의존적인 객채로 이루어진 제품군을 생산하는 인터페이스 제공
구상 클래스는 서브 클래스에서 만듬
📌 팩토리 쪽에만 집중하면 팩토리 메소드 패턴과 유사하지만, 클라이언트에서 추상 팩토리를 어떻게 사용하는지에 대한 관점으로 접근해야 목적과 처리점을 이해하기 쉽다.
abstract class PizzaAF {
var name: String = ""
var dough: Dough? = null
var sauce: Sauce? = null
var cheese: Cheese? = null
var clams: Clams? = null
var veggie: Veggie? = null
var tomato: Tomato? = null
abstract fun prepare()
fun bake() {
println("25분 동안 피자를 굽습니다.")
}
fun cut() {
println("피자를 8등분으로 자릅니다.")
}
fun box() {
println("피자 박스에 피자를 담고 있습니다.")
}
override fun toString(): String {
... 생략
}
}
일반 팩토리 메소드와 생성자의 구성은 비슷합니다.
달라진 부분이라면 dough, sauce, cheese, clams, veggie, tomato 변수가 각각의 AbstractProduct
로 변경되었다는 점 입니다.
해당 객체는 아래에서 자세히 구현하도록 하겠습니다.
interface PizzaIngredientFactory {
fun createDough(): Dough
fun createSauce(): Sauce
fun createCheese(): Cheese
fun createVeggie(): Veggie
fun createClams(): Clams
fun createTomato(): Tomato
}
추상 팩토리 클래스 또는 인터페이스를 통해 Dough, Sauce, Cheese, Veggie, Clams, Tomato
객체에 대한 서브 클래스를 리턴해 줍니다.
이는 객체지향(OOP)의 다형성을 아주 잘 활용한 방식이라 볼 수 있습니다.
자세한 구현은 ConcreteFactory
를 통해 구현해보도록 하겠습니다.
class NYPizzaIngredientFactory : PizzaIngredientFactory {
override fun createDough(): Dough = NYDough()
override fun createSauce(): Sauce = NYSauce()
override fun createCheese(): Cheese = NYCheese()
override fun createVeggie(): Veggie = NYVeggie()
override fun createClams(): Clams = NYClams()
override fun createTomato(): Tomato = NYTomato()
}
NYPizzaIngredientFactory(ConcreteFactory) 클래스를 만들고 PizzaIngredientFactory(AbstractFactory) 인터페이스를 상속 받습니다.
PizzaIngredientFactory의 함수를 override 하게 되고 여기서 NYPizza 스타일에 맞는 서브 Product 클래스를 넣어 줍니다.
PizzaAF 에 있는 Dough, Sauce, Cheese, Veggie, Clams, Tomato 객체를 Factory를 통해 만들었습니다.
interface Cheese {
override fun toString(): String
}
interface Clams {
override fun toString(): String
}
interface Dough {
override fun toString(): String
}
interface Sauce {
override fun toString(): String
}
interface Tomato {
override fun toString(): String
}
interface Veggie {
override fun toString(): String
}
간단하게 AbstractProduct
클래스들을 만들어 보았습니다.
이제 AbstractProduct
를 상속 받아 해당 객체를 구현해줄 서브 클래스들을 만들어 주어야 합니다.
class NYCheese : Cheese {
override fun toString(): String {
return "## 뉴욕 치즈 ##"
}
}
class ChicagoClams : Clams {
override fun toString(): String {
return "## 시카고 조개 ##"
}
}
... 등등
서브 클래스까지 모두 만들었습니다.
이제 Client를 만들어야 합니다.
제가 구현한 프로젝트에서 Client는 PizzaStore
클래스를 상속 받고 있는 클래스들 입니다.
해당 클래스의 상세 구현으로 넘어가 보겠습니다.
abstract class PizzaStoreAF {
// 팩토리 메소드와 같은 함수 입니다.
abstract fun createPizza(item: PizzaTypeAF): PizzaAF
fun orderPizza(type: PizzaTypeAF): PizzaAF {
val pizza: PizzaAF = createPizza(type)
println("--- Making a ${pizza.name} ---")
return pizza.apply {
prepare()
bake()
cut()
box()
}
}
}
class NYPizzaStore : PizzaStoreAF() {
override fun createPizza(item: PizzaTypeAF): PizzaAF {
val ingredientFactory = NYPizzaIngredientFactory()
return when(item) {
PizzaTypeAF.CHEESE -> {
CheesePizza(ingredientFactory).apply {
name = "뉴욕 치즈 피자"
}
}
PizzaTypeAF.CLAM -> {
ClamsPizza(ingredientFactory).apply {
name = "뉴욕 조개 피자"
}
}
PizzaTypeAF.TOMATO -> {
TomatoPizza(ingredientFactory).apply {
name = "뉴욕 토마토 피자"
}
}
PizzaTypeAF.VEGGIE -> {
VeggiePizza(ingredientFactory).apply {
name = "뉴욕 야채 피자"
}
}
}
}
}
class ChicagoPizzaStore : PizzaStoreAF() {
override fun createPizza(item: PizzaTypeAF): PizzaAF {
...
}
}
Client를 자세히 보면 제일 처음 다이어그램에서 처럼 AbstractFacotry
와 AbstractProduct
를 가지고 각각의 Pizza를 만들어 주는 것을 볼 수 있습니다.
Client 클래스인 PizzaStore
도 모두 만들었습니다.
직접 사용할 수 있도록 코드를 작성해 봅시다.
fun main() {
val nyStore = NYPizzaStore()
val chicagoStore = ChicagoPizzaStore()
var pizzaAF = nyStore.orderPizza(PizzaTypeAF.CHEESE)
println("A 주문 피자: $pizzaAF")
pizzaAF = chicagoStore.orderPizza(PizzaTypeAF.TOMATO)
println("B 주문 피자: $pizzaAF")
pizzaAF = chicagoStore.orderPizza(PizzaTypeAF.VEGGIE)
println("C 주문 피자: $pizzaAF")
pizzaAF = nyStore.orderPizza(PizzaTypeAF.CLAM)
println("D 주문 피자: $pizzaAF")
}
내가 원하는 Client 에서 원하는 Type의 피자를 주문하면 뉴욕 or 시카고 스타일의 피자를 만들어 줍니다.
사용하는 부분(직접 구현하는 부분)에서 if-else 또는 when 문을 사용하지 않고 쉽게 원하는 피자를 생성이 가능하게 되었습니다.
추가로 다른 종류의 피자를 추가한다면 추상 클래스 or 인터페이스를 추가하면 간단하게 확장이 가능하며, 기존 코드는 변경되지 않습니다.(OCP)
구현(Implement)보다 인터페이스(Interface)를 위한 코드 접근법을 제공.
sub class를 확장이 쉽다.
클라이언트 코드에서 구체적인 클래스의 의존성을 줄여주어 느슨한 결함이 가능함.