티스토리 뷰

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의 서브 클래스에서 결정하도록 한다.

 

  1. 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를 지키는 이상적 방법
    1. 변수에 구상 클래스의 레퍼런스를 저장하지 않기
      • 생성자 직접 호출하지 않고, 팩토리 패턴을 사용한다.
    2. 구상 클래스에서 유도된 클래스를 만들지 않기
      • 특정 구상 클래스에 의존하게 된다.
    3. 베이스 클래스에 이미 구현되어 있는 메소드를 오버라이드하지 않기
      • 제대로 추상화되지 않는다. 베이스 클래스에서는 서브 클래스들끼리 공통된 기능만 정의한다.

 

 

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
250x250
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
TAG
more
«   2024/10   »
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31
글 보관함