Builder pattern in TypeScript

Topics: General
Oct 5, 2012 at 3:10 PM

I tried to implmenent the Builder pattern in TypeScript. Things I noticed:

- no abstract classes. I worked around this with an interface and an "unvisible" class PizzaBuilder

- no protected. I worked around this with a getter to access instance variables in the base class.

Please help to improve my code:

 

builder.html

 

 

<!DOCTYPE html>

<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="utf-8" />
    <title>TypeScript HTML App</title>
    <link rel="stylesheet" href="app.css" type="text/css" />
    <script src="builder.js"></script>
</head>
<body>
    <h1>Typescript implementation of the Builder pattern</h1>
    <p><a href="http://en.wikipedia.org/wiki/Builder_pattern" target="_blank">Builder pattern on Wikipedia</a></p>
    <p>
        Output:
    </p>
    <div id="content"/>
</body>
</html>

 

 

builder.ts

// Typescript implementation of the Builder pattern
// http://en.wikipedia.org/wiki/Builder_pattern

module Patterns {
    export module Builder {

        export class Pizza {
            private dough: string;
            private sauce: string;
            private topping: string;

            setDough(dough: string): void {
                this.dough = dough;
            }

            setSauce(sauce: string): void {
                this.sauce = sauce;
            }

            setTopping(topping: string): void {
                this.topping = topping;
            }

            show(): string {
                return "dough: " + this.dough +
                       ", sauce: " + this.sauce +
                       ", topping: " + this.topping;
            }
        }

        export interface PizzaBuilderIF {
            getPizza(): Pizza;
            createNewPizzaProduct(): void;
            buildDough(): void;
            buildSauce(): void;
            buildTopping(): void;
        }

        // make PizzaBuilder class private to the Patterns.Builder module
        // no way to make it abstract :-)
        class PizzaBuilder implements PizzaBuilderIF {
            private pizza: Pizza;

            getPizza(): Pizza {
                return this.pizza;
            }

            createNewPizzaProduct(): void {
                this.pizza = new Pizza();
            }

            buildDough(): void {
            };

            buildSauce(): void {
            };

            buildTopping(): void {
            };
        }

        export class HawaiianPizzaBuilder extends PizzaBuilder {
            buildDough(): void {
                this.getPizza().setDough("cross");
            }

            buildSauce(): void {
                this.getPizza().setSauce("mild");
            }

            buildTopping(): void {
                this.getPizza().setTopping("ham+pineapple");
            }
        }

        export class SpicyPizzaBuilder extends PizzaBuilder {
            buildDough(): void {
                this.getPizza().setDough("pan baked");
            }

            buildSauce(): void {
                this.getPizza().setSauce("hot");
            }

            buildTopping(): void {
                this.getPizza().setTopping("pepperoni+salami");
            }
        }


        export class Waiter {
            private pizzaBuilder: PizzaBuilderIF;

            setPizzaBuilder(pb: PizzaBuilderIF): void {
                this.pizzaBuilder = pb;
            }
            getPizza(): Pizza {
                return this.pizzaBuilder.getPizza();
            }

            constructPizza(): void {
                this.pizzaBuilder.createNewPizzaProduct();
                this.pizzaBuilder.buildDough();
                this.pizzaBuilder.buildSauce();
                this.pizzaBuilder.buildTopping();
            }
        }
    }
}


class BuilderExample {
    private element: HTMLElement;

    constructor (element: HTMLElement) {
        this.element = element;
    }

    build(): void {
        var waiter: Patterns.Builder.Waiter = new Patterns.Builder.Waiter();
        var hawaiianPizzaBuilder: Patterns.Builder.PizzaBuilderIF = new Patterns.Builder.HawaiianPizzaBuilder();
        var spicyPizzaBuilder: Patterns.Builder.SpicyPizzaBuilder = new Patterns.Builder.SpicyPizzaBuilder();

        waiter.setPizzaBuilder(hawaiianPizzaBuilder);
        waiter.constructPizza();

        var pizza: Patterns.Builder.Pizza = waiter.getPizza();
        console.log(pizza.show());
        this.element.innerHTML = pizza.show();
    }
}

window.onload = () => {
    var el = document.getElementById('content');
    var builderExample = new BuilderExample(el);
    builderExample.build();
};

Coordinator
Oct 5, 2012 at 6:25 PM

TypeScript uses a structural type system, so using interfaces to mimic abstract classes is the preferred approach.  There is currently no support for protected, but you could submit a feature request in the issue tracker with a (preferably small) sample code showing where it's required.

Good luck!

Oct 6, 2012 at 3:27 AM

Here's how I implemented the TypeScript side of your code. It's almost verbatim as it's coded on Wikipedia but I lightened it up for JavaScript/TypeScript.  The thing with TypeScript is that it's JavaScript at the end of the day so you want to keep code size in mind...  The big changes were:

- I used an interface to define the Pizza as it's lighter weight but you could still use a class if needed.

- I dropped the PizzaBuilder.getPizza() accessor and just made PizzaBuilder.pizza a public field given there's no support for protected in TypeScript.

 

// "Product"
interface Pizza {
	dough?: string;
	sauce?: string;
	topping?: string;
}

 // "Abstract Builder"
 class PizzaBuilder {
	public pizza: Pizza;
 
    public createNewPizzaProduct(): void { this.pizza = {}; }
 
    public buildDough(): void {}
    public buildSauce(): void {}
    public buildTopping(): void {}
 }
 
 // "ConcreteBuilder"
 class HawaiianPizzaBuilder extends PizzaBuilder {
    public buildDough(): void   { this.pizza.dough = "cross"; }
    public buildSauce(): void   { this.pizza.sauce = "mild"; }
    public buildTopping(): void { this.pizza.topping = "ham+pineapple"; }
 }
 
 // "ConcreteBuilder"
 class SpicyPizzaBuilder extends PizzaBuilder {
    public buildDough(): void   { this.pizza.dough = "pan baked"; }
    public buildSauce(): void   { this.pizza.sauce = "hot"; }
    public buildTopping(): void { this.pizza.topping = "pepperoni+salami"; }
 }

 // "Director"
 class Waiter {
    private pizzaBuilder: PizzaBuilder;
 
    public setPizzaBuilder(pb: PizzaBuilder): void { this.pizzaBuilder = pb; }
    public getPizza(): Pizza { return this.pizzaBuilder.pizza; }
 
    public constructPizza(): void {
       this.pizzaBuilder.createNewPizzaProduct();
       this.pizzaBuilder.buildDough();
       this.pizzaBuilder.buildSauce();
       this.pizzaBuilder.buildTopping();
    }
 }
 
 // A customer ordering a pizza.
 var waiter = new Waiter();
 var hawaiianPizzaBuilder = new HawaiianPizzaBuilder();
 var spicyPizzaBuilder = new SpicyPizzaBuilder();
 
 waiter.setPizzaBuilder(hawaiianPizzaBuilder);
 waiter.constructPizza();
 var pizza = waiter.getPizza();