[디자인패턴] 빌더 패턴 (Builder Pattern) 예시 - 서브웨이

"A View of Villages depiected by 1800s Korean painting" from DALL-E 2

소개

서브웨이에 방문하면 손님은 빵 크기, 빵 종류, 추가토핑, 야채, 소스를 선택해야 하나의 샌드위치가 완성됩니다. 이와 같이 하나의 객체를 만드는 과정에서 옵션이 다양할 때 사용하는 패턴이 빌더 패턴 (Builder Pattern) 입니다. 이 포스팅에서 빌더 패턴을 설명하지는 않고, 빌더 패턴을 적용한 서브웨이 샌드위치를 생성하는 소스코드를 공유합니다.

 

소스코드는 타입스크립트를 기반으로 작성하였습니다.

열거체

const enum EBreadSize {
    SMALL = 1,
    LARGE = 2,
}

const enum EBreadType {
    WHITE = 1,
    WHEAT = 2,
    HONEY_OAT = 3,
    HEARTY_ITALIAN = 4,
    PARMESAN_OREGANO = 5,
    FLAT_BREAD = 6,
}

const enum ETopping {
    MEAT = 1,
    CHEESE = 2,
    AVOCADO = 3,
    PEPPERONI = 4,
    EGG_MAYO = 5,
    BACON = 6,
    OMELET = 7,
}

const enum EVegetable {
    LETTUCE = 1,
    TOMATO = 2,
    ONION = 3,
    OLIVE = 4,
    JALAPENO = 5,
    PIMENTO = 6,
    CUCUMBER = 7,
    PICKLE = 8,
    AVOCADO = 9,
}

const enum ESauce {
    RANCH = 1,
    MAYONNAISE = 2,
    SWEET_ONION = 3,
    HONEY_MUSTARD = 4,
    SWEET_CHILI = 5,
    HOT_CHILI = 6,
    SOUTHWEST_CHIPOTLE = 7,
    MUSTARD = 8,
    HORSERADISH = 9,
    OLIVE_OIL = 10,
    RED_WINE_VINEGAR = 11,
    SALT = 12,
    PEPPER = 13,
    SMOKE_BBQ = 14,
    ITALIAN_DRESSING = 15,
}

열거체 값에 대한 영문표기

const breadSizeEnglishMap = new Map<EBreadSize, string>([
    [EBreadSize.SMALL, 'small'],
    [EBreadSize.LARGE, 'large'],
])

const breadTypeEnglishMap = new Map<EBreadType, string>([
    [EBreadType.WHITE, 'white'],
    [EBreadType.WHEAT, 'wheat'],
    [EBreadType.HONEY_OAT, 'honey oat'],
    [EBreadType.HEARTY_ITALIAN, 'hearty italian'],
    [EBreadType.PARMESAN_OREGANO, 'parmesan oregano'],
    [EBreadType.FLAT_BREAD, 'flat bread'],
])

const toppingEnglishMap = new Map<ETopping, string>([
    [ETopping.MEAT, 'meat'],
    [ETopping.CHEESE, 'cheese'],
    [ETopping.AVOCADO, 'avocado'],
    [ETopping.PEPPERONI, 'pepperoni'],
    [ETopping.EGG_MAYO, 'egg mayo'],
    [ETopping.BACON, 'bacon'],
    [ETopping.OMELET, 'omelet'],
])

const vegetableEnglishMap = new Map<EVegetable, string>([
    [EVegetable.LETTUCE, 'lettuce'],
    [EVegetable.TOMATO, 'tomato'],
    [EVegetable.ONION, 'onion'],
    [EVegetable.OLIVE, 'olive'],
    [EVegetable.JALAPENO, 'jalapeno'],
    [EVegetable.PIMENTO, 'pimento'],
    [EVegetable.CUCUMBER, 'cucumber'],
    [EVegetable.PICKLE, 'pickle'],
    [EVegetable.AVOCADO, 'avocado'],
])

const sauceEnglishMap = new Map<ESauce, string>([
    [ESauce.RANCH, 'ranch'],
    [ESauce.MAYONNAISE, 'mayonnaise'],
    [ESauce.SWEET_ONION, 'sweet onion'],
    [ESauce.HONEY_MUSTARD, 'honey mustard'],
    [ESauce.SWEET_CHILI, 'sweet chili'],
    [ESauce.HOT_CHILI, 'hot chili'],
    [ESauce.SOUTHWEST_CHIPOTLE, 'southwest chipotle'],
    [ESauce.MUSTARD, 'mustard'],
    [ESauce.HORSERADISH, 'horseradish'],
    [ESauce.OLIVE_OIL, 'olive oil'],
    [ESauce.RED_WINE_VINEGAR, 'red wine vinegar'],
    [ESauce.SALT, 'salt'],
    [ESauce.PEPPER, 'pepper'],
    [ESauce.SMOKE_BBQ, 'smoke bbq'],
    [ESauce.ITALIAN_DRESSING, 'italian dressing'],
])

빌더 인터페이스

interface IBuilder {
    reset(): void
    setBreadSize(breadSize: EBreadSize): IBuilder
    setBreadType(breadType: EBreadType): IBuilder
    setToppings(toppings: ETopping[]): IBuilder
    setVegetables(vegetables: EVegetable[]): IBuilder
    setSauces(sauces: ESauce[]): IBuilder
}

샌드위치 빌더 클래스

class SandwichBuilder implements IBuilder {
    private breadSize: EBreadSize
    private breadType: EBreadType
    private toppings: ETopping[]
    private vegetables: EVegetable[]
    private sauces: ESauce[]
    constructor() {
        this.reset()
    }
    public reset(): void {
        this.breadSize = EBreadSize.SMALL
        this.breadType = EBreadType.WHEAT
        this.toppings = []
        this.vegetables = []
        this.sauces = []
    }
    public setBreadSize(breadSize: EBreadSize): IBuilder {
        this.breadSize = breadSize
        return this
    }
    public setBreadType(breadType: EBreadType): IBuilder {
        this.breadType = breadType
        return this
    }
    public setToppings(toppings: ETopping[]): IBuilder {
        this.toppings = toppings
        return this
    }
    public setVegetables(vegetables: EVegetable[]): IBuilder {
        this.vegetables = vegetables
        return this
    }
    public setSauces(sauces: ESauce[]): IBuilder {
        this.sauces = sauces
        return this
    }
    public getResult(): string {
        return `${breadSizeEnglishMap.get(this.breadSize)} ${breadTypeEnglishMap.get(this.breadType)} ${this.toppings
            .map(topping => toppingEnglishMap.get(topping))
            .join('+')} ${this.vegetables.map(vegetable => vegetableEnglishMap.get(vegetable)).join('+')} ${this.sauces
            .map(sauce => sauceEnglishMap.get(sauce))
            .join('+')}`
    }
}

레시피를 알고 있는 디렉터 클래스

class Director {
    public buildSandwich1(builder: IBuilder): void {
        builder
            .setBreadSize(EBreadSize.SMALL)
            .setBreadType(EBreadType.WHEAT)
            .setToppings([ETopping.MEAT, ETopping.CHEESE])
            .setVegetables([EVegetable.ONION, EVegetable.TOMATO])
            .setSauces([ESauce.RANCH])
    }

    public buildSandwich2(builder: IBuilder): void {
        builder
            .setBreadSize(EBreadSize.LARGE)
            .setBreadType(EBreadType.PARMESAN_OREGANO)
            .setToppings([ETopping.EGG_MAYO, ETopping.PEPPERONI, ETopping.MEAT])
            .setVegetables([EVegetable.LETTUCE, EVegetable.TOMATO, EVegetable.AVOCADO, EVegetable.CUCUMBER])
            .setSauces([ESauce.HORSERADISH, ESauce.SWEET_ONION, ESauce.MAYONNAISE])
    }
}

사용 예시

const director = new Director()
const builder = new SandwichBuilder()
director.buildSandwich1(builder)
console.log(builder.getResult())
builder.reset()
director.buildSandwich2(builder)
console.log(builder.getResult())

결과

small wheat meat+cheese onion+tomato ranch
large parmesan oregano egg mayo+pepperoni+meat lettuce+tomato+avocado+cucumber horseradish+sweet onion+mayonnaise