티스토리 뷰
728x90
Factory Pattern
- 객체 생성을 처리하는 패턴.
- 객체를 사용하는 코드와 생성하는 코드를 분리한다.
팩토리 패턴이 필요한 경우
class Client {
fun orderPizza(type: String): Pizza? {
var pizza: Pizza? = null
when (type) {
"cheese" -> pizza = CheesePizza()
"pepperoni" -> pizza = PepperoniPizza()
"clam" -> pizza = ClamPizza()
"veggie" -> pizza = VeggiePizza()
}
pizza?.prepare()
pizza?.bake()
pizza?.cut()
pizza?.box()
return pizza
}
}
- 구상 클래스를 생성하는 코드가 다른 클래스에서도 필요한 경우, 중복 코드가 많아진다.
- 인터페이스를 구현한 구상 클래스를 바탕으로 코드를 작성하면, 변경 사항이 있는 경우 모든 구상 클래스를 수정해야 한다.
- 인터페이스를 바탕으로 코드를 작성하면, 변경 사항이 있는 경우 특정 인터페이스만 수정하면 된다.
- → 개방-폐쇄 원칙(OCP): 확장에는 열려있고 변경에는 닫혀있음
- 구상 클래스 (concrete class)
- 추상 클래스와 반대의 개념
- 인터페이스나 추상 클래스를 구현하여 추상적이지 않고 구체적이고 명확하다.
- 생성자를 호출하여 인스턴스를 생성할 수 있다.
Simple Factory
- 단순히 구상 클래스를 생성하는 부분을 분리하는 방식
class SimplePizzaFactory {
fun createPizza(type: String): Pizza? {
return when (type) {
"cheese" -> CheesePizza()
"pepperoni" -> PepperoniPizza()
"clam" -> ClamPizza()
"veggie" -> VeggiePizza()
else -> null
}
}
}
class PizzaStore(private val factory: SimplePizzaFactory = SimplePizzaFactory()) {
fun orderPizza(type: String): Pizza? {
val pizza = factory.createPizza(type)
pizza ?: return null
pizza.prepare()
pizza.bake()
pizza.cut()
pizza.box()
return pizza
}
}
- 여러 곳에서 클래스 인스턴스를 생성하는 기능이 필요하다면, 팩토리를 통해 인스턴스를 생성할 수 있다.
- 구현을 변경해야 할 경우 팩토리 클래스 한 곳만 고치면 된다.
클래스 다이어그램
Factory Method Pattern
- 객체를 생성하기 위한 인터페이스를 정의한다. 어떤 클래스의 인스턴스를 만들지는 인터페이스의 서브클래스에서 결정한다.
- → 클래스 인스턴스의 생성을 서브클래스에게 맡겨서 객체 생성을 캡슐화한다.
- 어떤 서브클래스를 선택했는지에 따라 인스턴스의 종류가 결정된다.
- 구상 클래스의 의존성을 줄여줌으로써 느슨한 결합을 도와준다. (Loose Coupling)
- 인스턴스를 생성하는 부분과 사용하는 부분을 분리한다. Product의 클래스 타입을 추가하더라도, Creator의 코드는 변경할 필요가 없다.
- Simple Factory와 달리 팩토리 메소드 패턴은 팩토리 메소드가 정의되어 있는 추상 클래스를 확장해서 만든다.
구조
- Product
- 제품 클래스를 추상화시킨 인터페이스
- ConcreteProduct
- Product를 구현하여, 생성될 수 있는 제품을 구현해야 한다.
- ConcreteCreator에서 ConcreteProduct를 생성한다.
- Creator
- 생성된 제품을 가지고 원하는 일을 하기 위한 기능이 구현되어 있는 추상 클래스
- 실제 제품을 만드는 메소드는 추상 메소드로 정의한다.
- ConcreteCreator
- Creator를 상속하여, 제품을 생산하는 추상 메소드를 구현해야 한다.
- 실제 어떤 클래스의 인스턴스가 생성되는지 Creator는 알지 못하며, ConcreteCreator만 알고 있다.
팩토리 메소드 예시
abstract fun factoryMethod(type: String) : Product
- 추상 메소드로 선언해서, 서브 클래스가 인스턴스 생성을 책임지도록 한다.
- 클라이언트에서 (상위 클래스) 실제로 생성되는 구상 인스턴스가 무엇인지 알 수 없게 한다.
- 생성할 인스턴스의 종류를 매개변수로 선택할 수 있다.
- 특정 클래스의 인스턴스를 반환하며, 그 인스턴스는 보통 상위 클래스의 다른 메소드에서 쓰인다.
예제 코드
- 심플 팩토리의 예제 코드 활용.
- 여러 지점의 피자 가게가 있고, 지점마다 메뉴가 다르다고 가정.
- 어떤 피자를 만들지는 Creator의 서브 클래스에서 결정하도록 한다.
- Creator 추상 클래스 생성
abstract class PizzaStore {
fun orderPizza(type: String): Pizza? {
var pizza = createPizza(type)
pizza?.prepare()
pizza?.bake()
pizza?.cut()
pizza?.box()
return pizza
}
abstract fun createPizza(type: String): Pizza? // 팩토리 메소드
}
- PizzaStore에서 orderPizza()를 호출하고, 어떤 피자를 만들지를 결정하는 createPizza()는 각 서브 클래스 Store에서 구현하도록 한다.
- orderPizza()에서 Pizza 객체에 대한 처리를 하지만, 구체적으로 어떤 타입의 Pizza인지는 알지 못한다. 구체적인 타입은 Store의 서브 클래스에서 createPizza()를 구현하여 결정한다.
- → PizzaStore와 Pizza는 완전히 분리되어 있다.
2. Creator 추상 클래스 구현
class NYPizzaStore : PizzaStore() {
override fun createPizza(type: String): Pizza? {
return when (type) {
"cheese" -> NYCheesePizza()
"veggie" -> NYVeggiePizza()
"clam" -> NYClamPizza()
"pepperoni" -> NYPepperoniPizza()
else -> null
}
}
}
- PizzaStore를 상속받는다.
- 각 지점마다 피자 스타일이 다를 수 있으므로, createPizza()를 각 Store에서 구현한다.
- 해당 지점의 피자(NYCheesePizza, NYVeggiePizza...)를 만드는 모든 방법이 캡슐화되어 있다.
3. 실제 사용
fun test() {
val nyStore = NYPizzaStore()
val chicagoStore = ChicagoPizzaStore()
var pizza = nyStore.orderPizza("cheese")
println(pizza?.name)
pizza = chicagoStore.orderPizza("cheese")
println(pizza?.name)
}
- 각 PizzaStore를 생성하고, orderPizza()를 호출한다.
- orderPizza()는 상위 클래스인 PizzaStore의 메소드이며, orderPizza() 안의 createPizza()는 세부적인 PizzaStore에서 구현한 메소드다.
클래스 다이어그램
Dependency Inversion Principle (의존 역전 원칙)
- 추상화된 것에 의존하고, 구상 클래스에 의존하지 않아야 한다.
- PizzaStore가 모든 구상 클래스에 의존하고 있다.
- 구상 클래스가 추가된다면 더 많은 클래스에 의존하게 된다.
- 팩토리 메소드를 통해 DIP 원칙을 만족시킬 수 있다.
- PizzaStore는 Pizza 인터페이스에 의존하고, 모든 구상 클래스도 Pizza 인터페이스에 의존하게 된다.
- DIP를 지키는 이상적 방법
- 변수에 구상 클래스의 레퍼런스를 저장하지 않기
- 생성자 직접 호출하지 않고, 팩토리 패턴을 사용한다.
- 구상 클래스에서 유도된 클래스를 만들지 않기
- 특정 구상 클래스에 의존하게 된다.
- 베이스 클래스에 이미 구현되어 있는 메소드를 오버라이드하지 않기
- 제대로 추상화되지 않는다. 베이스 클래스에서는 서브 클래스들끼리 공통된 기능만 정의한다.
- 변수에 구상 클래스의 레퍼런스를 저장하지 않기
Abstract Factory Pattern
- 제품군을 생성하기 위한 인터페이스를 제공한다. 실제 제품을 생성하는 클래스는 인터페이스를 구현하여 사용한다.
- 같은 Product여도 팩토리에 따라 다른 방식으로 구현할 수 있다.
- 다른 결과가 필요하면 다른 팩토리를 사용하면 된다. 클라이언트 코드를 전혀 바꾸지 않고 여러가지 Product를 생산할 수 있다.
구조
예제 코드
- 팩토리 메소드 패턴의 예제 코드 활용.
- 피자 가게의 지점마다 사용하는 재료가 다르다고 가정.
- 각 지점마다 어떤 재료를 생성할지에 대한 팩토리를 만든다.
1. AbstractFactory 인터페이스 생성
interface PizzaIngredientFactory {
fun createDough() : Dough
fun createSauce() : Sauce
fun createCheese() : Cheese
fun createVeggies() : Array<Veggies>
fun createPepperoni() : Pepperoni
fun createClam() : Clams
}
- 재료를 만드는 PizzaIngredientFactory 인터페이스를 생성한다.
- 지점마다 달라지는 재료들은 PizzaIngredientFactory의 서브 클래스에서 구현한다.
2. AbstractFactory 인터페이스 구현
class NYPizzaIngredientFactory : PizzaIngredientFactory {
override fun createDough(): Dough {
return ThinCrustDough()
}
override fun createSauce(): Sauce {
return MarinaraSauce()
}
override fun createCheese(): Cheese {
return ReggianoCheese()
}
override fun createVeggies(): Array<Veggies> {
return arrayOf(Garlic(), Onion(), Mushroom(), RedPepper())
}
override fun createPepperoni(): Pepperoni {
return SlicedPepperoni()
}
override fun createClam(): Clams {
return FreshClams()
}
}
- 지점에서 사용할 재료에 맞게 PizzaIngredientFactory 인터페이스의 메소드를 구현한다.
3. ConcreteFactory을 통해 Product 생성
class NYPizzaStore : PizzaStore() {
override fun createPizza(type: String): Pizza? {
val ingredientFactory = NYPizzaIngredientFactory()
return when (type) {
"cheese" -> CheesePizza(ingredientFactory)
"veggie" -> VeggiePizza(ingredientFactory)
"clam" -> ClamPizza(ingredientFactory)
"pepperoni" -> PepperoniPizza(ingredientFactory)
else -> null
}
}
}
- 지점 별 PizzaStore에서는 재료 팩토리를 생성하고, 그 팩토리를 피자의 생성자로 전달한다.
- 생성자로 들어간 팩토리에 따라 피자의 재료를 선택한다.
4. Product 구현
class CheesePizza(private val ingredientFactory: PizzaIngredientFactory) : Pizza() {
override fun prepare() {
dough = ingredientFactory.createDough()
sauce = ingredientFactory.createSauce()
cheese = ingredientFactory.createCheese()
}
}
class ClamPizza(private val ingredientFactory: PizzaIngredientFactory) : Pizza() {
override fun prepare() {
dough = ingredientFactory.createDough()
sauce = ingredientFactory.createSauce()
cheese = ingredientFactory.createCheese()
clams = ingredientFactory.createClam()
}
}
- 각 지점 별 피자 클래스(NYCheesePizza, ChicagoCheesePizza...)를 생성하지 않는다.
- 하나의 피자 클래스(CheesePizza)에서 IngredientFactory가 어떤 서브 클래스인지 따라 피자의 재료를 선택한다.
5. 실제 사용
fun test() {
val nyStore = NYPizzaStore()
val chicagoStore = ChicagoPizzaStore()
var pizza = nyStore.orderPizza("cheese")
println(pizza?.name)
pizza = chicagoStore.orderPizza("cheese")
println(pizza?.name)
}
- 클라이언트 코드는 동일하다.
- PizzaStore의 orderPizza()안에서 createPizza()를 호출하면, 그 지점의 재료 팩토리가 생성되고 재료 팩토리에 맞게 피자가 생성된다.
- 클래스 다이어그램
팩토리 메소드 패턴과 차이
- 두 패턴 모두 클라이언트 코드와 구상 클래스의 생성을 분리하는 역할을 한다.
- 팩토리 메소드 패턴은 클래스를 확장 + 오버라이드를 사용하지만, 추상 팩토리 패턴은 객체 구성을 사용한다.
- 팩토리 메소드 패턴은 제품 하나만 생성하고, 추상 팩토리 패턴은 제품군을 생성한다.
728x90
'app > design pattern' 카테고리의 다른 글
[디자인 패턴] Singleton Pattern (0) | 2023.07.05 |
---|---|
[디자인 패턴] Observer Pattern (0) | 2023.07.05 |