Search code examples
javascriptbuilder-pattern

Javascript Builder Pattern Not Seeing Declared Varialble


I'm trying to implement the Builder Pattern to generate a JSON string of options that are passed into a library to generate widgets. I can't understand why at console.log below that this.options is undefined.

let Options = function(options) {
  this.options = options;
}

let OptionsObjectBuilder = function () {

  let options;

  return {
      addConstantLineToValueAxis: function (lineValue) {
          console.log(this.options); // EQUALS UNDEFINED SO CAN'T ADD TO THIS OBJECT
          this.options.valueAxis.constantLine.value = lineValue;
          return this;
      },
      build: function () {
          return new Options(this.options);
      }
  };
};

let option = new OptionsObjectBuilder().addConstantLineToValueAxis(1000000000).build();


Solution

  • There are two different ways for the builder to store the temporary states:

    • In the builder object itself (by setting this.options =)
    • In a closure (by setting options =)

    The closure example has the benefit that the temporary builder state is not accessible to the outside.

    You can use either way, as long as the builder uses them from the correct place. I will fix the broken example from the post you mentioned. I think they started using closures, and it didn't work because the param name was shadowing the closure variable and they ended up getting confused switching to using this instead. They forgot to update their build() function to read from the correct place.

    Using builder object state - Exposes internal state

    let Task = function(name, description, finished, dueDate) {
    
        this.name = name;
        this.description = description;
        this.finished = finished;
        this.dueDate = dueDate;
    }
    
    let TaskBuilder = function () {
        return {
            setName: function (name) {
                this.name = name;
                return this;
            },
            setDescription: function (description) {
                this.description = description;
                return this;
            },
            setFinished: function (finished) {
                this.finished = finished;
                return this;
            },
            setDueDate: function (dueDate) {
                this.dueDate = dueDate;
                return this;
            },
            build: function () {
                return new Task(this.name, this.description, this.isFinished, this.dueDate);
            }
        };
    };
    
    let builder = new TaskBuilder().setName('Task A').setDescription('finish book')
        .setDueDate(new Date(2019, 5, 12));
    let task = builder.build();
    // Notice the builder does expose the name/description... properties
    console.log({builder, task});

    Using closure variables - Hides internal state

    let Task = function(name, description, finished, dueDate) {
    
        this.name = name;
        this.description = description;
        this.finished = finished;
        this.dueDate = dueDate;
    }
    
    let TaskBuilder = function () {
    
        let name;
        let description;
        let isFinished = false;
        let dueDate;
    
        return {
            setName: function (pName) {
                name = pName;
                return this;
            },
            setDescription: function (pDescription) {
                description = pDescription;
                return this;
            },
            setFinished: function (pFinished) {
                finished = pFinished;
                return this;
            },
            setDueDate: function (pDueDate) {
                dueDate = pDueDate;
                return this;
            },
            build: function () {
                return new Task(name, description, isFinished, dueDate);
            }
        };
    };
    
    let builder = new TaskBuilder().setName('Task A').setDescription('finish book')
        .setDueDate(new Date(2019, 5, 12));
    let task = builder.build();
    // Can't see the name/description... properties on the builder, just the methods
    console.log({builder, task});