우리는 객체를 생성함으로써 코드를 만들어 나갑니다.
하지만, 문제가 있습니다.
→ new를 통해 만들어진 객체를 변경하거나 확장해야할 때, 우리는 코드를 다시 확인하고 추가/제거 해야합니다.
→ 이런 식으로 코드를 만들면 관리 및 갱신이 어려워지고 오류가 생길 가능성이 높아집니다..!
바뀔 수 있는 부분을 찾아내서 바뀌지 않는 부분하고 분리시킵시다!
fun orderPizza(type: String): Pizza {
lateinit var pizza : Pizza
// 이 부분은 맨날 바뀌어야 하는 부분. 새로운 메뉴가 나오거나 메뉴가 삭제된다면 해당 코드는 매일 변합니다.
if(type == "cheese") {
pizza = CheesePizza()
} else if(type == "greek") {
pizza = GreekPizza()
} else if(type == "pepperoni") {
pizza = PepperoniPizza()
}
/* 추가될 수도 있습니다!
else if(type == "clam") {
pizza = new ClamPizza()
}
*/
// 여기는 무슨 짓을 하더라도 피자가 준비되고, 배달되기 위해서는 필요한 부분.
pizza.prepare()
pizza.bake()
pizza.cut()
pizza.box()
return pizza
}
class PizzaStore(val factory: SimplePizzaFactory) {
fun orderPizza(type: String): Pizza {
pizza = factory.createPizza(type)
pizza.prepare()
pizza.bake()
pizza.cut()
pizza.box()
}
}
class SimplePizzaFactory {
fun createPizza(type: String): Pizza {
lateinit var pizza: Pizza
if(type == "cheese") {
pizza = CheesePizza()
}
} else if(type == "greek") {
pizza = GreekPizza()
}
} else if(type == "pepperoni") {
pizza = PepperoniPizza()
}
return pizza
}
}
모든 팩토리 패턴에서는 객체 생성을 캡슐화합니다!
서브클래스(의 메서드)에서 어떤 클래스를 만들지 결정하게 함으로써(== 맡기는 것) 객체 생성을 캡슐화합니다!
→ 객체를 생성하기 위한 인터페이스를 정의하는 것!
→ 어떠한 클래스의 인스턴스를 만들지는 서브클래스에서 결정하게 만듭니다.
이 패턴에서 등장하는 클래스의 종류는 두가지로 나눌 수 있습니다.
PizzaStore: 추상 생산자 클래스
ChicagoPizzaStore & NYPizzaStore: 구상 생산자(Concrete creator) 클래스
Pizza: 팩토리에서 생산하는 모든 제품(product)
~~pizza: 구상 클래스
화살표의 모양은 알고 계신 것과 다를 겁니다...! 양해부탁.
구상 클래스 인스턴스를 만드는 일은 Concrete 친구들이 책임집니다.
실제로 제품을 만드는 클래스는 ConcreteCreator밖에 없습니다!
제품군을 만들 때 사용하는 패턴(== product class를 만들 때 사용하는 팩토리 패턴!)
인터페이스를 이용하여 서로 연관된, 또는 의존하는 객체를 구상 클래스를 지정하지 않고도 생성이 가능하다!!
설계도에서 보면, product 구상클래스들이 factory interface를 받아서 객체를 구성하는 것을 알 수 있다!
자, 코드로 살펴봅시다.
package factory
abstract class Pizza {
abstract var name: String
abstract var dough: String
abstract var sauce: String
abstract var toppings: ArrayList<String>
abstract fun prepare() // 상속받을 구상클래스에서 객체를 구성하기 위해 필요한 메서드
open fun bake() {
println("bake 25분 걸려요.")
}
open fun cut() {
println("pizza를 잘라서 드릴게요~")
}
open fun box() {
println("박스 포장을 합니다.")
}
}
package factory
class NYCheesePizza(
private val pizzaIngredientFactory: PizzaIngredientFactory // factory 클래스를 주입받는다.
): Pizza() {
override var name: String = "1KG 치즈 피자"
override lateinit var dough: String
override lateinit var sauce: String
override lateinit var toppings: ArrayList<String>
override fun prepare() { // 주입받은 factory 클래스를 통하여 객체를 구성한다.
println("맛있는 " + name + " 준비 중")
dough = pizzaIngredientFactory.createDough()
sauce = pizzaIngredientFactory.createSauce()
toppings = pizzaIngredientFactory.createVeggies()
}
override fun cut() {
println("치즈 피자 맛있겠다.")
}
}
package factory
interface PizzaIngredientFactory {
fun createDough(): String
fun createSauce(): String
fun createCheese(): String
fun createVeggies(): ArrayList<String>
fun createPepperoni(): String
fun createClam(): String
}
package factory
class NYPizzaIngredientFactory: PizzaIngredientFactory {
override fun createDough(): String {
return "ThinCrustDough"
}
override fun createSauce(): String {
return "MarinaraSauce"
}
override fun createCheese(): String {
return "ReggianoCheese"
}
override fun createVeggies(): ArrayList<String> {
return mutableListOf("Garlic", "Onion") as ArrayList<String>
}
override fun createPepperoni(): String {
return "SlicedPepperoni"
}
override fun createClam(): String {
return "FreshClam"
}
}
Q) 신메뉴로 피자에 민초를 엄청 넣고 싶은데... 그러면 어떡하죠?
A) 어쩔 수 없어요. interface가 수정되어야합니다..!
Q) 엥 그러면 모든 서브클래스가 수정되어야하는 거 아닌가요?
A) 제품군을 생성하는 추상 팩토리 패턴은 인터페이스도 매우 큰편(==인터페이스 내부에 있는 것들이 다양하다!)이기 때문에 감수해야합니다.
???) 이거 완전 팩토리 메서드 패턴의 생산자 클래스잖아!
종종 사용되는 경우가 있습니다. 추상 팩토리 패턴 안에도 제품을 생산하기 위한 메서드가 있기 때문에, 생산자 클래스가 사용되는 것은 자연스러운 일입니다.