본문 바로가기

IT/헤드퍼스트 디자인패턴

[디자인패턴] 팩토리패턴

안녕하세요 남갯입니다


오늘은 팩토리 패턴에 대해 포스팅해보려고합니다.


사건의발단

피자가게를 운영하고 있고 피자가게의 코드를 아래와 같이 만들었습니다.


fun orderPizza(): Pizza {
val pizza = Pizza().apply {
prepare()
bake()
cut()
bake()
}
return pizza
}


하지만 피자에는 종류는 여러개이므로 코드에 피자종류를 추가하게 되었습니다.


enum class PizzaType {
CHEESE, GREEK, PEPPRONI
}


fun orderPizza(type: PizzaType): Pizza {
var pizza: Pizza

when (type) {
PizzaType.CHEESE -> pizza = CheessPizza()
PizzaType.GREEK -> pizza = GreekPizza()
PizzaType.PEPPRONI -> pizza = PepperoniPizza()
}

pizza.apply {
prepare()
bake()
cut()
box()
}
return pizza
}

문제점

* 하지만  밑에 어떤종류인지랑 상관없는 준비하고 굽고 자르고 박싱하는 메소드는 피자의 종류와 상관없이 바뀌지 않지만

위의 피자종류는 종류를 구성하는 피자가 변경되었을 경우 코드를 수정해야합니다.


SimplePizzaFactory 를 통해

이전에 배웠던 다자인 패턴을 이용해서 변경되는 부분인 객체 생성부분을 빼서 캡슐화를 하게됩니다.

class SimplePizzaFactory {
fun createPizza(type: PizzaType): Pizza {
var pizza: Pizza
when (type) {
PizzaType.CHEESE -> pizza = CheessPizza()
PizzaType.GREEK -> pizza = GreekPizza()
PizzaType.PEPPRONI -> pizza = PepperoniPizza()
}
return pizza
}
}

피자 생성하는부분을 캡슐화 함으로서 orderpizza() 메소드 이외에 다른클래스에서도 이용할때 이 팩토리를 가져다 쓸 수 있습니다.

재사용성이 높아진것이죠. 또한 각각의 클래스에 만들어 둘경우 클래스에 대한 생성코드가 변경되면 각각의 클래스에 들어가 수정해야하지만

팩토리로 빼둠으로서 생성하는 코드를 한곳에서 관리 가능하도록 만든것이죠


아까 위의 orderPizza 메소드를 감쌌던 PizzaStore를 보겠습니다.


class PizzaStore(val factory: SimplePizzaFactory) {

fun orderPizza(type: PizzaType): Pizza {
val pizza: Pizza = factory.createPizza(type)
pizza.apply {
prepare()
bake()
cut()
box()
}
return pizza
}
}

이렇게 PizzaStore 클래스에서 SimplePizzaFactory를 통해 피자 객체를 만들면서 변하는 부분을 캡슐화하였습니다.

지금 위에서 한 내용을 SimpleFactory 라고 하는데  디자인패턴이라고 할 수 는 없습니다. 프로그래밍의 관용구에 가깝다고 합니다.


이렇게 배운내용을가지고 팩토리 메소트 패턴 과 추상 팩토리패턴 을 알아보도록 하겠습니다.


사건의 발단 2

위의 SimpleFactory 를 이용해서 피자사업이 성공해서 분점을 내려고 했습니다. 뉴욕과 시카고에 분점을 내려고합니다.

하지만 각 지역마다 특성과 입맛이 다르므로 다른스타일의 피자를 완성해야합니다.


어떻게 해야할까?

아까 배운 simpleFactory 를 이용해 뉴욕스타일의 Factory, 시카고스타일의 Factory,  캘리포니아스타일의 Factory를 만들어서 

PizzaStore에서 사용하면 어떻게 될까요?  분점마다의 각각의 방식이 달라지거나 자르는걸 까먹거나 그렇게 되어버렸습니다.

그래서 프레임워크로(특정 순서를 만들어서 그 내부에서 동작하도록 만드는것) 만들어서 가게와 제작과정을 합쳐보도록 하겠습니다.


팩토리 메소드 패턴

abstract class PizzaStore() {
fun orderPizza(type: PizzaType): Pizza {
val pizza: Pizza = createPizza(type)

pizza.apply {
prepare()
bake()
cut()
box()
}
return pizza
}

abstract fun createPizza(type: PizzaType): Pizza
}


클래스를 abstract 추상 클래스로 만듬으로서 createPizza를 상속받은 Store에서 상속받아서 각각 오버라이딩을 통해 구현하도록 합니다.

class NewYorkPizzaStore() : PizzaStore() {
override fun createPizza(type: PizzaType): Pizza {
var pizza: Pizza
when (type) {
PizzaType.CHEESE -> pizza = NYStyleCheesePizza()
PizzaType.GREEK -> pizza = NYStyleGreekPizza()
PizzaType.PEPPRONI -> pizza = NYStylePepperoniPizza()
}
return pizza
}
}

class ChicagoPizzaStore() : PizzaStore() {
override fun createPizza(type: PizzaType): Pizza {
var pizza: Pizza
when (type) {
PizzaType.CHEESE -> pizza = ChicagoStyleCheesePizza()
PizzaType.GREEK -> pizza = ChicagoStyleGreekPizza()
PizzaType.PEPPRONI -> pizza = ChicagoStylePepperoniPizza()
}
return pizza
}
}


이렇게 만들게되면 PizzaStore 클래스를 추상클래스로 만들어서 상속을받아 서브클래스를 만들기전까지 구상클래스는 만들어지지 않습니다

Pizza클래스의 입장에서도 Pizza클래스는 추상클래스이기때문에 orderPizza에서 어떤식으로 동작하고 있는지를 전혀 알수 없으므로 

Pizza와 PizzaStore는 완전히 분리되어 있다고 볼 수 있습니다. 실제 createPizza() 메소드에서 어떤 피자가 만들어지는지는 어떤 Store에서

주문하느냐에 따라 달라지니까요


아까 분리했던  Pizza 추상클래스를 구현해보겠습니다.

abstract class Pizza(
var name: String = "",
var dough: String = "",
var saure: String = "",
var toppings: ArrayList<String> = arrayListOf()
) {

fun prepare() {
println("prepare $name")
println("tossing dough $dough")
println("adding saure $saure")
println("adding toppings ")
for (i in toppings)
println("adding toppings $i")

}

open fun bake() {
println("Bake for 25 minute at 350 ")
}

open fun cut() {
println("cut cut pizza into diagonal slices")
}

open fun box() {
println("place pizza in pizzaStore")
}
}

이 피자를 통해 상속받은 구상 클래스들 두개

class NYStyleCheesePizza() : Pizza() {
init {
name = "NY Style Sauce and Cheese Pizza"
dough = "Thin Crust Dough"
saure = "Marinara Sauce"
toppings.add("Grated Reggiano Cheese")
}
}

class ChicagoStyleCheesePizza() : Pizza() {
init {
name = "Chichago Style Deep Dish Cheese Pizza"
dough = "Extra Thick Crust Dough"
saure = "Plum Tomato Sauce"
toppings.add("Shredded Mozzarella Cheese")
}

override fun cut() {
println("cut cut pizza into square slices")
}
}

이렇게 Pizza(제품) 클래스를 만들었습니다.

중요한점

이렇게 Creator 생산자 클래스인 PizzaStore 클래스와  Product 제품 클래스인 Pizza 클래스를 추상화함으로써

서브클래스에서 클래스를 결정하게 함으로써 객체의 생성을 캡슐화 했습니다. 

위에서도 말했듯이 두개의 추상클래스는 서로에 대해 모르면서 완벽히 분리된 코드(병렬적으로 구성) 됐습니다.


팩토리 메소드 패턴의 정의? 

위에서 만든 패턴을 팩토리 메소트 패턴이라고 합니다.

객체를 생성하기위한 인터페이스(자바의 인터페이스아님)를 정의하는데, 어떤 클래스의 인스턴스를 만들지를 서브클래스에서 결정하게 만듭니다.

팩토리 패턴을 이용하면 클래스와 인스턴스를 만드는 일을 서브클래스에게 맡기는 것입니다.

1. Creator 추상클래스에 구현되어있는 다른메소드에서는 팩토리를 통해 생산된 제품을 가지고 필요한 작업을 처리

2. 실제 구현하고 제품을 생성하는 일은 서브클래스만 가능함 (추상클래스로 만들기 때문에)



사건의 발단3

PizzaStore 이 모양새를 갖추고 있고 성공의 배경은 신선한 재료였습니다. 하지만 몇몇분점에서 싼재료를 쓰면서 마진율을 올린다고합니다.

그래서 원재료 공장에서 분점까지 재료를 배달하기로 했습니다. 하지만 같은 레드소스여도 시카고와 뉴욕의 레드소스는 다른 문제점이 있습니다.


interface PizzaIngredientFactory {
fun createDough(): Dough
fun createSauce(): Sauce
fun createCheese(): Cheese
fun createVeggies(): Array<Viggies>
fun createPepperoni(): Pepperoni
fun createClam(): Clams
}

재료를 만드는 공장을 만들고

interface Dough
interface Sauce
interface Cheese
interface Viggies
interface Pepperoni
interface Clams

의존성 역전(DIP)을 위해 인터페이스로 선언합니다.

class ThinCrustDough : Dough
class MarinaraSauce : Sauce
class ReggianoCheese : Cheese
class Garlic : Viggies
class Onion : Viggies
class Mushroom : Viggies
class RedPapper : Viggies
class SlicedPepperoni : Pepperoni
class FreshClams : Clams

이 재료를 받은 구체적인 클래스를 만들고

class NYPizzaIngredientFactory : PizzaIngredientFactory {
override fun createDough(): Dough = ThinCrustDough()
override fun createSauce(): Sauce = MarinaraSauce()
override fun createCheese(): Cheese = ReggianoCheese()
override fun createVeggies(): Array<Viggies> = arrayOf(Garlic(), Onion(), Mushroom(), RedPapper())
override fun createPepperoni(): Pepperoni = SlicedPepperoni()
override fun createClam(): Clams = FreshClams()
}

이 공장을 상속받아 뉴욕의 원재료 공장을 만들었습니다.

팩토리의 준비가 끝나면 재료를 생산할 준비가 되어 Pizza 클래스를 팩토리에서만 생산한 재료만 사용되도록 고쳐야합니다.


팩토리메소드를 이용했을때는 NYCheesePizza 클래스와 ChicagoCheesePizza 클래스를 따로 만들었지만 이 두 피자클래스의 차이는

지역적으로 나눈다는 차이점밖에 없고, 피자를 이루는 요소인 반죽 소스 치즈의 완성품임을 알수있습니다.

따라서 지역별로 나누지말고 각자의 공장에서 커버를 처줍니다.

class CheesePizza(val pizzaIngredientFactory: PizzaIngredientFactory) : Pizza {
override fun prepare() {
println("Preparing $name")
dough = pizzaIngredientFactory.createDough()
sauce = pizzaIngredientFactory.createSauce()
cheese = pizzaIngredientFactory.createCheese()
}
}

치즈피자는 성분을 통해 팩토리에서 받아오고 어느 공장을 넣어서 생성했느냐에 따라 다른 치즈피자가 완성되게 되는것입니다.

Clam 피자도 만들어보면

class ClamPizza(val pizzaIngredientFactory: PizzaIngredientFactory) : Pizza() {
override fun prepare() {
println("Preparing $name")
dough = pizzaIngredientFactory.createDough()
sauce = pizzaIngredientFactory.createSauce()
cheese = pizzaIngredientFactory.createCheese()
clams = pizzaIngredientFactory.createClam()
}
}

거의 동일하게 만들수 있습니다.

이제 다시 PizzaStore으로 돌아가보면


class NYPizzaStore : PizzaStore() {
override fun createPizza(type: PizzaType): Pizza {
var pizza: Pizza
val pizzaIngredientFactory = NYPizzaIngredientFactory()
if (type == PizzaType.CHEESE) {
pizza = CheesePizza(pizzaIngredientFactory)
pizza.name = "NewYork Style Cheese Pizza"
} else if (type == PizzaType.CLAM) {
pizza = ClamPizza(pizzaIngredientFactory)
pizza.name = "NewYork Style Cheese Pizza"
}else{
throw Exception()
}
return pizza
}
}

뉴욕 피자 스토어를 만들어서 전달해줍니다. 

이렇게하게되면 뉴욕의 제품을 만드는 팩토리에서 직접 재료를 선택하고 그 재료를 PizzaStore에게 전달하여

원하는 성분에 맞춰 피자를 만들수 있게 됩니다.

이렇게 하게되면 시카고도 동일하게 재료를 선택하고 팩토리를 전달하는 방식으로 구현 가능합니다.


추상 팩토리 패턴의 정의? 

위에서 만든 패턴을 추상 팩토리 패턴이라고 합니다.

인터페이스를 이용하여 서로 연관된 또는 의존하는 객체를 구상클래스로 지정하지 않고도 생성가능합니다.



팩토리 메소드패턴과 추상팩토리패턴의 공통/차이점

차이점

팩토리메소드패턴은 클래스를 써서 제품을 만든다.

- 상속을 통해 객체를 만든다.


추상 팩토리패턴에서는 객체를 써서 제품을 생산

- 구성을 통해 만든다.

- 제품군을 만들기위한 추상 형식을 제공하므로 확대하거나 추가할때 인터페이스에 대한 변경이 필요


공통점

- 둘다 객체를 만드는 일을한다.

- 클라이언트와 구상형식을 분리해주는 역할

- DIP 의존성 역전을 하는형태




------------과제 ---------


메소드 팩토리 

fun main1() {
val KRRamenStore = KRRamenStore()
KRRamenStore.orderRamen(RamenType.SINRAMEN)

val JPRmenStore = JPRamneStore()
JPRmenStore.orderRamen(RamenType.SINRAMEN)
}

abstract class Ramen(
var noodle: String = "",
var sauce: String = "",
var water: String = ""
) {
fun prepare() {
println("prepare")
}

fun putNoodle() {
println("put Noodle $noodle")
}

fun putWater() {
println("put water $water")
}

open fun boil() {
println("boil 3 minute")
}

fun putSauce() {
println("put Sauce $sauce")
}
}

class KRSinRamen : Ramen() {
init {
noodle = "KR style Normal Noodle"
sauce = "KR style SinRamen Sauce"
water = "450ml"
}
}

class JPSinRamen : Ramen() {
init {
noodle = "KR style Normal Noodle"
sauce = "KR style SinRamen Sauce"
water = "500ml"
}
}

class KRNuguri : Ramen() {
init {
noodle = "KR style Thick Noodle"
sauce = "KR style Nuguri Sauce"
water = "550ml"
}

override fun boil() {
println("must have boil 4 minute because of Thick Noodle")
}
}

class JPNuguri : Ramen() {
init {
noodle = "JP style Thick Noodle"
sauce = "JP style Nuguri Sauce"
water = "530ml"
}

override fun boil() {
println("must have boil 4 minute because of Thick Noodle")
}
}

enum class RamenType {
NUGURI, SINRAMEN
}


abstract class RamenStore {
lateinit var ramen: Ramen
fun orderRamen(ramenType: RamenType) {
println("order Ramen")
ramen = createRamen(ramenType)
ramen.prepare()
ramen.boil()
ramen.putSauce()
ramen.putNoodle()
}

abstract fun createRamen(ramenType: RamenType): Ramen
}

class KRRamenStore : RamenStore() {
override fun createRamen(ramenType: RamenType): Ramen {
when (ramenType) {
RamenType.NUGURI -> {
return KRNuguri()
}
RamenType.SINRAMEN -> {
return KRSinRamen()
}
}
}
}

class JPRamneStore : RamenStore() {
override fun createRamen(ramenType: RamenType): Ramen {
when (ramenType) {
RamenType.NUGURI -> {
return JPNuguri()
}
RamenType.SINRAMEN -> {
return JPSinRamen()
}
}
}
}




추상팩토리 라면집

fun main2() {
val KRRamenStore = KRRamenStore()
KRRamenStore.orderRamen(RamenType.SINRAMEN)

val JPRmenStore = JPRamenStore()
JPRmenStore.orderRamen(RamenType.NUGURI)
}

interface RamenIngedientFactory {
fun putWater(): Water
fun putSauce(): Sauce
fun putNoodle(): Noodle
}

interface Water
interface Sauce
interface Noodle

class Water500 : Water
class Water450 : Water
class SinSauce : Sauce
class NuguriSauce : Sauce
class ThickNoodle : Noodle
class NormalNoodle : Noodle
class ThinNoodle : Noodle


class KRSinRamenIngedientFactory : RamenIngedientFactory {
override fun putWater(): Water = Water450()
override fun putSauce(): Sauce = SinSauce()
override fun putNoodle(): Noodle = NormalNoodle()
}

class JPSinRamenIngedientFactory : RamenIngedientFactory {
override fun putWater(): Water = Water450()
override fun putSauce(): Sauce = SinSauce()
override fun putNoodle(): Noodle = ThinNoodle()
}


class KRNugoriIngedientFactory : RamenIngedientFactory {
override fun putWater(): Water = Water500()
override fun putSauce(): Sauce = NuguriSauce()
override fun putNoodle(): Noodle = ThickNoodle()
}

class JPNugoriIngedientFactory : RamenIngedientFactory {
override fun putWater(): Water = Water500()
override fun putSauce(): Sauce = NuguriSauce()
override fun putNoodle(): Noodle = ThickNoodle()
}

abstract class Ramen() {
var name: String? = null
var water: Water? = null
var sauce: Sauce? = null
var noodle: Noodle? = null
abstract fun prepare()

fun putNoodle() {
println("put Noodle ")
}

fun putWater() {
println("put water ")
}

open fun boil() {
println("boil 3 minute")
}

fun putSauce() {
println("put Sauce ")
}
}

class SinRamen(val ramenIngedientFactory: RamenIngedientFactory) : Ramen() {
override fun prepare() {
println("Preparing $name")
water = ramenIngedientFactory.putWater()
sauce = ramenIngedientFactory.putSauce()
noodle = ramenIngedientFactory.putNoodle()
}
}

class Nuguri(val ramenIngedientFactory: RamenIngedientFactory) : Ramen() {
override fun prepare() {
println("Preparing $name")
water = ramenIngedientFactory.putWater()
sauce = ramenIngedientFactory.putSauce()
noodle = ramenIngedientFactory.putNoodle()
}
}


abstract class RamenStore {
lateinit var ramen: Ramen
fun orderRamen(ramenType: RamenType) {
println("order Ramen")
ramen = createRamen(ramenType)
ramen.prepare()
ramen.putWater()
ramen.boil()
ramen.putSauce()
ramen.putNoodle()
}

abstract fun createRamen(ramenType: RamenType): Ramen
}

class KRRamenStore : RamenStore() {
override fun createRamen(ramenType: RamenType): Ramen {
var ramen: Ramen
var ramenIngedientFactory: RamenIngedientFactory
when (ramenType) {
RamenType.SINRAMEN -> {
ramenIngedientFactory = KRSinRamenIngedientFactory()
ramen = SinRamen(ramenIngedientFactory)
ramen.name = "Kr SinRamen"
}
RamenType.NUGURI -> {
ramenIngedientFactory = KRNugoriIngedientFactory()
ramen = Nuguri(ramenIngedientFactory)
ramen.name = "Kr Nuguri"
}
else -> {
throw Exception()
}
}
return ramen
}
}

class JPRamenStore : RamenStore() {
override fun createRamen(ramenType: RamenType): Ramen {
var ramen: Ramen
var ramenIngedientFactory: RamenIngedientFactory
when (ramenType) {
RamenType.SINRAMEN -> {
ramenIngedientFactory = JPSinRamenIngedientFactory()
ramen = SinRamen(ramenIngedientFactory)
ramen.name = "JP SinRamen"
}
RamenType.NUGURI -> {
ramenIngedientFactory = JPNugoriIngedientFactory()
ramen = Nuguri(ramenIngedientFactory)
ramen.name = "JP Nuguri"
}
else -> {
throw Exception()
}
}
return ramen
}
}


enum class RamenType {
NUGURI, SINRAMEN
}