팩토리 패턴

노지환·2022년 2월 8일
0

디자인패턴

목록 보기
1/2

우리는 객체를 생성함으로써 코드를 만들어 나갑니다.

하지만, 문제가 있습니다.

→ 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
    }
}

장점

  • 구현을 변경해야 하는 경우에 여기저기 다 들어가서 고칠 필요가 없습니다! → 위에 보이는 Factory 클래스만 고치면 됩니다.

뭔가 부족한데...?

  • Factory와 Store가 따로 있다보니, 새로운 Store가 만들어질 때마다 변하면 안되는 부분들이 개발자에 따라 바뀔 수도 있는 위험이 있습니다.
  • Store와 Factory를 유연성이 있으면서도 하나로 묶을 수 있는 방법은 없을까요?

알아가기

모든 팩토리 패턴에서는 객체 생성을 캡슐화합니다!


팩토리 메서드 패턴

서브클래스(의 메서드)에서 어떤 클래스를 만들지 결정하게 함으로써(== 맡기는 것) 객체 생성을 캡슐화합니다!

→ 객체를 생성하기 위한 인터페이스를 정의하는 것!

→ 어떠한 클래스의 인스턴스를 만들지는 서브클래스에서 결정하게 만듭니다.

이 패턴에서 등장하는 클래스의 종류는 두가지로 나눌 수 있습니다.

생산자(Creator) 클래스

PizzaStore: 추상 생산자 클래스

  • 상속받은 서브 클래스들에서 객체를 생산하기 위해 구현할 팩토리 메서드를 정의합니다.
  • 생산자 자체에서 어떤 제품 클래스가 만들어질지 미리 알 수 없습니다!

ChicagoPizzaStore & NYPizzaStore: 구상 생산자(Concrete creator) 클래스

  • createPizza() 메서드가 팩토리 메서드입니다! 이 메서드에서 객체를 생산합니다.

제품(Product) 클래스

Pizza: 팩토리에서 생산하는 모든 제품(product)

~~pizza: 구상 클래스

팩토리 메서드 패턴의 설계도

화살표의 모양은 알고 계신 것과 다를 겁니다...! 양해부탁.

구상 클래스 인스턴스를 만드는 일은 Concrete 친구들이 책임집니다.

실제로 제품을 만드는 클래스는 ConcreteCreator밖에 없습니다!


추상 팩토리 패턴

제품군만들 때 사용하는 패턴(== product class를 만들 때 사용하는 팩토리 패턴!)

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

설계도

설계도에서 보면, product 구상클래스들이 factory interface를 받아서 객체를 구성하는 것을 알 수 있다!

자, 코드로 살펴봅시다.

abstract product class

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("박스 포장을 합니다.")
    }
}

product class

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("치즈 피자 맛있겠다.")
    }
}
  • factory를 주입받을 때, interface 클래스를 작성해놓으면 우리가 필요한 구상클래스가 무엇인지 모르고 사용할 수 있다!

factory interface

package factory

interface PizzaIngredientFactory {

    fun createDough(): String
    fun createSauce(): String
    fun createCheese(): String
    fun createVeggies(): ArrayList<String>
    fun createPepperoni(): String
    fun createClam(): String
}

factory class

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) 제품군을 생성하는 추상 팩토리 패턴은 인터페이스도 매우 큰편(==인터페이스 내부에 있는 것들이 다양하다!)이기 때문에 감수해야합니다.

추상 팩토리 패턴은 팩토리 메서드 패턴을 사용한다?

???) 이거 완전 팩토리 메서드 패턴의 생산자 클래스잖아!

종종 사용되는 경우가 있습니다. 추상 팩토리 패턴 안에도 제품을 생산하기 위한 메서드가 있기 때문에, 생산자 클래스가 사용되는 것은 자연스러운 일입니다.

profile
기초가 단단한 프로그래머 -ing

0개의 댓글