CS/디자인 패턴

팩토리 패턴

KDGdev 2023. 1. 24. 23:46

디자인 패턴


디자인 패턴이란 프로그램을 설계할 때 발생했던 문제점들을 해결할 수 있도록 하나의 ‘규약’ 형태로 만들어 놓은 것

 

중복 코드 방지, 의존성 제거, 유지 보수 개선 등 코드의 더 좋은 구조를 만드는 것을 도와주는 코드 작성방법입니다.

 

팩토리 패턴


팩토리 패턴은 두가지가 있습니다.
팩토리 메소드 패턴, 추상 팩토리 패턴 각각 설명해드리겠습니다.

팩토리 메소드 패턴

팩토리 메소드 패턴은 객체를 직접 생성하지 않고 다른 클래스로부터 얻는 패턴입니다.

 

커피 판매점 객체가 있다고 했을 때의 예를 kotlin 코드로 보겠습니다.

 

class CoffeeShop {
    fun sellCoffee(type: String) {
        val coffee = when(type){
            "americano" -> Americano()
            "latte" -> Latte()
            ...
            else -> ...
        }
        coffee.prepare()
        coffee.sell()

    }
}

 

위 코드의 문제점은 커피 메뉴를 추가하거나 제거할 때 when문을 계속해서 변경해야 하는 것입니다.
객체를 직접 생성하였기 때문에 객체의 종류를 추가하거나 삭제할 때 그 객체를 생성한 객체 내부의 코드가 변경되므로 좋은 객체지향프로그래밍의 설계가 아닙니다.


단일 책임 원칙(SRP)을 위반했다고도 볼 수 있고 개방 폐쇄 원칙(OCP)을 위반했다고도 볼 수 있습니다.
따라서 객체 생성 부분인 when문을 분리합니다.

 

class CoffeeFactory {
    fun createCoffee(type: String){
        return when(type){
              "americano" -> Americano()
              "latte" -> Latte()
              ...
              else -> ...
        }
    }
}

class CoffeeShop {
    private val coffeeFactory = CoffeeFactory()
    fun sellCoffee(type: String) {
        val coffee = coffeeFactory.createCoffee(type)
        coffee.prepare()
        coffee.sell()
    }
}

 

팩토리 메소드 패턴을 사용하여 커피 객체의 생성을 CoffeeFactory 객체에서 하므로 커피 메뉴의 변경이 있더라도 CoffeeShop 객체는 수정하지 않아도 됩니다.
의존성 주입과도 비슷해 보입니다.

 

추상 팩토리 패턴

추상 팩토리 패턴은 여러 개의 객체가 필요할 경우 연관된 객체의 생성을 강제하거나 재사용하기 위해 서로 연관된 객체들의 조합을 만드는 인터페이스를 제공하는 패턴입니다.

 

커피 객체로 예를 들어 보겠습니다.

 

class Coffee {
    fun setCoffee(type: String){
        var bean: Bean()
        var base: Base()
        if(type == "americano"){
            bean = SourBean()
            base = Water()
        }
        else if(type == "latte") {
            bean = SweetBean()
            base = Milk()
        }
        ... // bean과 base를 사용하는 코드 생략
    }
}

 

해당 예시에서는 setCoffee 함수를 위해 Bean 객체와 Base 객체를 필요로 합니다.

마찬가지로 이 객체들의 또 다른 조합을 만들거나 없애기 위해 if문을 계속해서 수정해야 합니다.

 

interface CoffeeFactory {
    fun getBean(): CoffeBean
    fun getBase(): CoffeeBase
}

object AmericanoFactory : CoffeeFactory {
    override fun getBean() = SourBean()
    override fun getBase() = Water()
}

object LatteFactory : CoffeeFactory {
    override fun getBean() = SweetBean()
    override fun getBase() = Milk()
}

class Coffee(private val coffeeFactory: CoffeeFactory()) {
    fun setCoffee(type: String){
        val bean = coffeeFactory.getBean()
        val base = coffeeFactory.getBase()
        ... // bean과 base를 사용하는 코드 생략
    }
}

 

이러한 단점을 해결하기 위해 객체의 조합을 인터페이스로 제공합니다. 따라서 새로운 조합이 필요할 때는 CoffeeFactory를 상속받는 싱글톤 객체를 생성하면 됩니다.
※싱글톤 객체로 생성하는 이유는 새로운 조합을 추가하거나 삭제하는 일은 있어도 정해놓은 조합 자체를 다른 곳에서 변경할 일이 없기때문에 싱글톤 패턴을 사용하여 인스턴스 생성 비용을 줄이기 위함입니다.

 

※ 잘못된 정보, 혹은 다른 의견이 있다면 댓글로 말해주세요. 감사합니다.