3

I've got a simple example of using a fluent interface to attach events in JavaScript

function attach(fn){
  let handler,to;
  handler = fn;
  attach.to = function (el) {
    to = document.getElementById(el);
    return attach;
  }
  attach.on = function (type) {
    to.addEventListener(type,fn);
    return attach;
  }
  return attach;
}

attach(()=>console.log("hello world")).to("div").on("click");

currently this is dependent on the order in which the functions are called. This example is simple enough with only two other attributes that need to be set, but what if there were several more? Is there a preferred pattern for letting functions being called in any order and then executing the result when n number of properties have been set?

2 Answers 2

2

As long as you're returning the same thing you can keep calling the same things on it (that's, hopefully, fluent). If you wish to control the order of what's allowed to be called you must return something that only allows what you want to allow to be called.

These are called iDSL's: internal Domain Specific Languages. Rather than always returning the same thing with the same set of functions you return a different object that has different functions. When this language is built inside another language (like javascript) we call the DSL internal.

The Javascript world isn't strongly typed. You won't get told you can't call a function until you run the code. But, other than the lack of hints when people are shooting themselves in the foot, this works. You will effectively keep people from calling stuff out of order.

I haven't used them much in Javascript but this java question I asked a long time ago links to some good example patterns that give the kind of control I think you're talking about. Other than your IDE not being as helpful, everything would work the same in Javascript.

Javascript may let you implement this in a different way. I can imagine directly enabling and disabling functions you want available, dynamically. Not sure how ugly that would be.

If you've seen jQuery in action you've seen fluent in Javascript. You wouldn't be the first to extend this into iDSL. Here's an example.

2

The way this is usually achieved is by using the return values of the fluent methods to encode the current state and the valid transitions of the builder's state machine. In other words: you return an object which only has the methods for the properties that haven't been set yet.

Unfortunately, this can easily lead to a combinatorial explosion of objects, as you can see here in my "little" example for an email builder with four properties: sender, recipient, subject, and message.

"use strict";

function mail() { return new VoidEmailBuilder(); }

class EmailBuilder {
  constructor(old, param) {
    for (const prop in old)   this[prop] = old[prop];
    for (const prop in param) this[prop] = param[prop];
  }

  toString() { return `Email from "${this.sender}" to "${this.recipient}" regarding "${this.subject}" saying "${this.message}".`; }
}

class VoidEmailBuilder extends EmailBuilder {
  from(sender)    { return new EmailBuilderWithSender(   this, {sender:    sender}   ); }
  to  (recipient) { return new EmailBuilderWithRecipient(this, {recipient: recipient}); }
  re  (subject)   { return new EmailBuilderWithSubject(  this, {subject:   subject}  ); }
  msg (message)   { return new EmailBuilderWithMessage(  this, {message:   message}  ); }
}

class EmailBuilderWithSender extends EmailBuilder {
  to  (recipient) { return new EmailBuilderWithSenderAndRecipient(this, {recipient: recipient}); }
  re  (subject)   { return new EmailBuilderWithSenderAndSubject(  this, {subject:   subject}  ); }
  msg (message)   { return new EmailBuilderWithSenderAndMessage(  this, {message:   message}  ); }
}

class EmailBuilderWithRecipient extends EmailBuilder {
  from(sender)    { return new EmailBuilderWithSenderAndRecipient( this, {sender:  sender} ); }
  re  (subject)   { return new EmailBuilderWithRecipientAndSubject(this, {subject: subject}); }
  msg (message)   { return new EmailBuilderWithRecipientAndMessage(this, {message: message}); }
}

class EmailBuilderWithSubject extends EmailBuilder {
  from(sender)    { return new EmailBuilderWithSenderAndSubject(   this, {sender:    sender}   ); }
  to  (recipient) { return new EmailBuilderWithRecipientAndSubject(this, {recipient: recipient}); }
  msg (message)   { return new EmailBuilderWithSubjectAndMessage(  this, {message:   message}  ); }
}

class EmailBuilderWithMessage extends EmailBuilder {
  from(sender)    { return new EmailBuilderWithSenderAndMessage(   this, {sender:    sender}   ); }
  to  (recipient) { return new EmailBuilderWithRecipientAndMessage(this, {recipient: recipient}); }
  re  (subject)   { return new EmailBuilderWithSubjectAndMessage(  this, {subject:   subject}  ); }
}

class EmailBuilderWithSenderAndRecipient extends EmailBuilder {
  re  (subject)   { return new EmailBuilderWithSenderAndRecipientAndSubject(this, {subject: subject}); }
  msg (message)   { return new EmailBuilderWithSenderAndRecipientAndMessage(this, {message: message}); }
}

class EmailBuilderWithSenderAndSubject extends EmailBuilder {
  to  (recipient) { return new EmailBuilderWithSenderAndRecipientAndSubject(this, {recipient: recipient}); }
  msg (message)   { return new EmailBuilderWithSenderAndSubjectAndMessage(  this, {message:   message}  ); }
}

class EmailBuilderWithSenderAndMessage extends EmailBuilder {
  to  (recipient) { return new EmailBuilderWithSenderAndRecipientAndMessage(this, {recipient: recipient}); }
  re  (subject)   { return new EmailBuilderWithSenderAndSubjectAndMessage(  this, {subject:   subject}  ); }
}

class EmailBuilderWithRecipientAndSubject extends EmailBuilder {
  from(sender)    { return new EmailBuilderWithSenderAndRecipientAndSubject( this, {sender:  sender} ); }
  msg (message)   { return new EmailBuilderWithRecipientAndSubjectAndMessage(this, {message: message}); }
}

class EmailBuilderWithRecipientAndMessage extends EmailBuilder {
  from(sender)    { return new EmailBuilderWithSenderAndRecipientAndMessage( this, {sender:  sender} ); }
  re  (subject)   { return new EmailBuilderWithRecipientAndSubjectAndMessage(this, {subject: subject}); }
}

class EmailBuilderWithSubjectAndMessage extends EmailBuilder {
  from(sender)    { return new EmailBuilderWithSenderAndSubjectAndMessage(   this, {sender:    sender   }); }
  to  (recipient) { return new EmailBuilderWithRecipientAndSubjectAndMessage(this, {recipient: recipient}); }
}

class EmailBuilderWithSenderAndRecipientAndSubject extends EmailBuilder {
  msg (message)   { return new Email(this, {message: message}); }
}

class EmailBuilderWithSenderAndRecipientAndMessage extends EmailBuilder {
  re  (subject)   { return new Email(this, {subject: subject}); }
}

class EmailBuilderWithSenderAndSubjectAndMessage extends EmailBuilder {
  to  (recipient) { return new Email(this, {recipient: recipient}); }
}

class EmailBuilderWithRecipientAndSubjectAndMessage extends EmailBuilder {
  from(sender)    { return new Email(this, {sender:  sender}); }
}

class Email extends EmailBuilder {
  send() { console.log(`Sending this email: ${this}`); }
}

That's 16 classes to encode the following state machine:EmailBuilder State Machine

This allows you to send an email using any of the following combinations:

mail().from("Me").to("You").re("ES Builder").msg("Bla!").send();
mail().from("Me").to("You").msg("Bla!").re("ES Builder").send();
mail().from("Me").re("ES Builder").to("You").msg("Bla!").send();
mail().from("Me").re("ES Builder").msg("Bla!").to("You").send();
mail().from("Me").msg("Bla!").to("You").re("ES Builder").send();
mail().from("Me").msg("Bla!").re("ES Builder").to("You").send();
mail().to("You").from("Me").re("ES Builder").msg("Bla!").send();
mail().to("You").from("Me").msg("Bla!").re("ES Builder").send();
mail().to("You").re("ES Builder").from("Me").msg("Bla!").send();
mail().to("You").re("ES Builder").msg("Bla!").from("Me").send();
mail().to("You").msg("Bla!").from("Me").re("ES Builder").send();
mail().to("You").msg("Bla!").re("ES Builder").from("Me").send();
mail().re("ES Builder").from("Me").to("You").msg("Bla!").send();
mail().re("ES Builder").from("Me").msg("Bla!").to("You").send();
mail().re("ES Builder").to("You").from("Me").msg("Bla!").send();
mail().re("ES Builder").to("You").msg("Bla!").from("Me").send();
mail().re("ES Builder").msg("Bla!").from("Me").to("You").send();
mail().re("ES Builder").msg("Bla!").to("You").from("Me").send();
mail().msg("Bla!").from("Me").to("You").re("ES Builder").send();
mail().msg("Bla!").from("Me").re("ES Builder").to("You").send();
mail().msg("Bla!").to("You").from("Me").re("ES Builder").send();
mail().msg("Bla!").to("You").re("ES Builder").from("Me").send();
mail().msg("Bla!").re("ES Builder").from("Me").to("You").send();
mail().msg("Bla!").re("ES Builder").to("You").from("Me").send();

If you forget to set any one of the properties, you will get an error:

 mail().from("Me").to("You").re("ES Builder").send();
 mail().from("Me").to("You").msg("Bla!").send();
 mail().from("Me").re("ES Builder").to("You").send();
 mail().from("Me").re("ES Builder").msg("Bla!").send();
 mail().from("Me").msg("Bla!").to("You").send();
 mail().from("Me").msg("Bla!").re("ES Builder").send();
 mail().to("You").from("Me").re("ES Builder").send();
 mail().to("You").from("Me").msg("Bla!").send();
 mail().to("You").re("ES Builder").from("Me").send();
 mail().to("You").re("ES Builder").msg("Bla!").send();
 mail().to("You").msg("Bla!").from("Me").send();
 mail().to("You").msg("Bla!").re("ES Builder").send();
 mail().re("ES Builder").from("Me").to("You").send();
 mail().re("ES Builder").from("Me").msg("Bla!").send();
 mail().re("ES Builder").to("You").from("Me").send();
 mail().re("ES Builder").to("You").msg("Bla!").send();
 mail().re("ES Builder").msg("Bla!").from("Me").send();
 mail().re("ES Builder").msg("Bla!").to("You").send();
 mail().msg("Bla!").from("Me").to("You").send();
 mail().msg("Bla!").from("Me").re("ES Builder").send();
 mail().msg("Bla!").to("You").from("Me").send();
 mail().msg("Bla!").to("You").re("ES Builder").send();
 mail().msg("Bla!").re("ES Builder").from("Me").send();
 mail().msg("Bla!").re("ES Builder").to("You").send();

And if you try to specify a property twice, you will also get an error:

 mail().from("Me").to("You").re("ES Builder").msg("Bla!").from("Again");
 mail().from("Me").to("You").re("ES Builder").msg("Bla!").to("Again");
 mail().from("Me").to("You").re("ES Builder").msg("Bla!").re("Again");
 mail().from("Me").to("You").re("ES Builder").msg("Bla!").msg("Again");
 mail().from("Me").to("You").msg("Bla!").re("ES Builder").from("Again");
 mail().from("Me").to("You").msg("Bla!").re("ES Builder").to("Again");
 mail().from("Me").to("You").msg("Bla!").re("ES Builder").re("Again");
 mail().from("Me").to("You").msg("Bla!").re("ES Builder").msg("Again");
 mail().from("Me").re("ES Builder").to("You").msg("Bla!").from("Again");
 mail().from("Me").re("ES Builder").to("You").msg("Bla!").to("Again");
 mail().from("Me").re("ES Builder").to("You").msg("Bla!").re("Again");
 mail().from("Me").re("ES Builder").to("You").msg("Bla!").msg("Again");
 mail().from("Me").re("ES Builder").msg("Bla!").to("You").from("Again");
 mail().from("Me").re("ES Builder").msg("Bla!").to("You").to("Again");
 mail().from("Me").re("ES Builder").msg("Bla!").to("You").re("Again");
 mail().from("Me").re("ES Builder").msg("Bla!").to("You").msg("Again");
 mail().from("Me").msg("Bla!").to("You").re("ES Builder").from("Again");
 mail().from("Me").msg("Bla!").to("You").re("ES Builder").to("Again");
 mail().from("Me").msg("Bla!").to("You").re("ES Builder").re("Again");
 mail().from("Me").msg("Bla!").to("You").re("ES Builder").msg("Again");
 mail().from("Me").msg("Bla!").re("ES Builder").to("You").from("Again");
 mail().from("Me").msg("Bla!").re("ES Builder").to("You").to("Again");
 mail().from("Me").msg("Bla!").re("ES Builder").to("You").re("Again");
 mail().from("Me").msg("Bla!").re("ES Builder").to("You").msg("Again");
 mail().to("You").from("Me").re("ES Builder").msg("Bla!").from("Again");
 mail().to("You").from("Me").re("ES Builder").msg("Bla!").to("Again");
 mail().to("You").from("Me").re("ES Builder").msg("Bla!").re("Again");
 mail().to("You").from("Me").re("ES Builder").msg("Bla!").msg("Again");
 mail().to("You").from("Me").msg("Bla!").re("ES Builder").from("Again");
 mail().to("You").from("Me").msg("Bla!").re("ES Builder").to("Again");
 mail().to("You").from("Me").msg("Bla!").re("ES Builder").re("Again");
 mail().to("You").from("Me").msg("Bla!").re("ES Builder").msg("Again");
 mail().to("You").re("ES Builder").from("Me").msg("Bla!").from("Again");
 mail().to("You").re("ES Builder").from("Me").msg("Bla!").to("Again");
 mail().to("You").re("ES Builder").from("Me").msg("Bla!").re("Again");
 mail().to("You").re("ES Builder").from("Me").msg("Bla!").msg("Again");
 mail().to("You").re("ES Builder").msg("Bla!").from("Me").from("Again");
 mail().to("You").re("ES Builder").msg("Bla!").from("Me").to("Again");
 mail().to("You").re("ES Builder").msg("Bla!").from("Me").re("Again");
 mail().to("You").re("ES Builder").msg("Bla!").from("Me").msg("Again");
 mail().to("You").msg("Bla!").from("Me").re("ES Builder").from("Again");
 mail().to("You").msg("Bla!").from("Me").re("ES Builder").to("Again");
 mail().to("You").msg("Bla!").from("Me").re("ES Builder").re("Again");
 mail().to("You").msg("Bla!").from("Me").re("ES Builder").msg("Again");
 mail().to("You").msg("Bla!").re("ES Builder").from("Me").from("Again");
 mail().to("You").msg("Bla!").re("ES Builder").from("Me").to("Again");
 mail().to("You").msg("Bla!").re("ES Builder").from("Me").re("Again");
 mail().to("You").msg("Bla!").re("ES Builder").from("Me").msg("Again");
 mail().re("ES Builder").from("Me").to("You").msg("Bla!").from("Again");
 mail().re("ES Builder").from("Me").to("You").msg("Bla!").to("Again");
 mail().re("ES Builder").from("Me").to("You").msg("Bla!").re("Again");
 mail().re("ES Builder").from("Me").to("You").msg("Bla!").msg("Again");
 mail().re("ES Builder").from("Me").msg("Bla!").to("You").from("Again");
 mail().re("ES Builder").from("Me").msg("Bla!").to("You").to("Again");
 mail().re("ES Builder").from("Me").msg("Bla!").to("You").re("Again");
 mail().re("ES Builder").from("Me").msg("Bla!").to("You").msg("Again");
 mail().re("ES Builder").to("You").from("Me").msg("Bla!").from("Again");
 mail().re("ES Builder").to("You").from("Me").msg("Bla!").to("Again");
 mail().re("ES Builder").to("You").from("Me").msg("Bla!").re("Again");
 mail().re("ES Builder").to("You").from("Me").msg("Bla!").msg("Again");
 mail().re("ES Builder").to("You").msg("Bla!").from("Me").from("Again");
 mail().re("ES Builder").to("You").msg("Bla!").from("Me").to("Again");
 mail().re("ES Builder").to("You").msg("Bla!").from("Me").re("Again");
 mail().re("ES Builder").to("You").msg("Bla!").from("Me").msg("Again");
 mail().re("ES Builder").msg("Bla!").from("Me").to("You").from("Again");
 mail().re("ES Builder").msg("Bla!").from("Me").to("You").to("Again");
 mail().re("ES Builder").msg("Bla!").from("Me").to("You").re("Again");
 mail().re("ES Builder").msg("Bla!").from("Me").to("You").msg("Again");
 mail().re("ES Builder").msg("Bla!").to("You").from("Me").from("Again");
 mail().re("ES Builder").msg("Bla!").to("You").from("Me").to("Again");
 mail().re("ES Builder").msg("Bla!").to("You").from("Me").re("Again");
 mail().re("ES Builder").msg("Bla!").to("You").from("Me").msg("Again");
 mail().msg("Bla!").from("Me").to("You").re("ES Builder").from("Again");
 mail().msg("Bla!").from("Me").to("You").re("ES Builder").to("Again");
 mail().msg("Bla!").from("Me").to("You").re("ES Builder").re("Again");
 mail().msg("Bla!").from("Me").to("You").re("ES Builder").msg("Again");
 mail().msg("Bla!").from("Me").re("ES Builder").to("You").from("Again");
 mail().msg("Bla!").from("Me").re("ES Builder").to("You").to("Again");
 mail().msg("Bla!").from("Me").re("ES Builder").to("You").re("Again");
 mail().msg("Bla!").from("Me").re("ES Builder").to("You").msg("Again");
 mail().msg("Bla!").to("You").from("Me").re("ES Builder").from("Again");
 mail().msg("Bla!").to("You").from("Me").re("ES Builder").to("Again");
 mail().msg("Bla!").to("You").from("Me").re("ES Builder").re("Again");
 mail().msg("Bla!").to("You").from("Me").re("ES Builder").msg("Again");
 mail().msg("Bla!").to("You").re("ES Builder").from("Me").from("Again");
 mail().msg("Bla!").to("You").re("ES Builder").from("Me").to("Again");
 mail().msg("Bla!").to("You").re("ES Builder").from("Me").re("Again");
 mail().msg("Bla!").to("You").re("ES Builder").from("Me").msg("Again");
 mail().msg("Bla!").re("ES Builder").from("Me").to("You").from("Again");
 mail().msg("Bla!").re("ES Builder").from("Me").to("You").to("Again");
 mail().msg("Bla!").re("ES Builder").from("Me").to("You").re("Again");
 mail().msg("Bla!").re("ES Builder").from("Me").to("You").msg("Again");
 mail().msg("Bla!").re("ES Builder").to("You").from("Me").from("Again");
 mail().msg("Bla!").re("ES Builder").to("You").from("Me").to("Again");
 mail().msg("Bla!").re("ES Builder").to("You").from("Me").re("Again");
 mail().msg("Bla!").re("ES Builder").to("You").from("Me").msg("Again");

Unfortunately, it seems like at least WebStorm's type inference is not quite smart enough to give you correct autocompletion, unless you help it along with some type annotations:

"use strict";

/**
 * @returns {!VoidEmailBuilder}
 */
function mail() { return new VoidEmailBuilder(); }

class EmailBuilder {
  /**
   * @param {!EmailBuilder} old
   * @param {!{sender: string, recipient: string, subject: string, message: string}} param
   */
  constructor(old, param) {
    for (const prop in old)   this[prop] = old[prop];
    for (const prop in param) this[prop] = param[prop];
  }

  /**
   * @override
   * @final
   * @returns {!string}
   */
  toString() { return `Email from "${this.sender}" to "${this.recipient}" regarding "${this.subject}" saying "${this.message}".`; }
}

/** @final
 *  @extends EmailBuilder */
class VoidEmailBuilder extends EmailBuilder {
  /**
   * @param {!string} sender
   * @returns {!EmailBuilderWithSender}
   */
  from(sender)    { return new EmailBuilderWithSender(   this, {sender:    sender}   ); }

  /**
   * @param {!string} recipient
   * @returns {!EmailBuilderWithRecipient}
   */
  to  (recipient) { return new EmailBuilderWithRecipient(this, {recipient: recipient}); }

  /**
   * @param {!string} subject
   * @returns {!EmailBuilderWithSubject}
   */
  re  (subject)   { return new EmailBuilderWithSubject(  this, {subject:   subject}  ); }

  /**
   * @param {!string} message
   * @returns {!EmailBuilderWithMessage}
   */
  msg (message)   { return new EmailBuilderWithMessage(  this, {message:   message}  ); }
}

/** @final
 *  @extends EmailBuilder */
class EmailBuilderWithSender extends EmailBuilder {
  /**
   * @param {!string} recipient
   * @returns {!EmailBuilderWithSenderAndRecipient}
   */
  to  (recipient) { return new EmailBuilderWithSenderAndRecipient(this, {recipient: recipient}); }

  /**
   * @param {!string} subject
   * @returns {!EmailBuilderWithSenderAndSubject}
   */
  re  (subject)   { return new EmailBuilderWithSenderAndSubject(  this, {subject:   subject}  ); }

  /**
   * @param {!string} message
   * @returns {!EmailBuilderWithSenderAndMessage}
   */
  msg (message)   { return new EmailBuilderWithSenderAndMessage(  this, {message:   message}  ); }
}

/** @final
 *  @extends EmailBuilder */
class EmailBuilderWithRecipient extends EmailBuilder {
  /**
   * @param {!string} sender
   * @returns {!EmailBuilderWithSenderAndRecipient}
   */
  from(sender)    { return new EmailBuilderWithSenderAndRecipient( this, {sender:  sender} ); }

  /**
   * @param {!string} subject
   * @returns {!EmailBuilderWithRecipientAndSubject}
   */
  re  (subject)   { return new EmailBuilderWithRecipientAndSubject(this, {subject: subject}); }

  /**
   * @param {!string} message
   * @returns {!EmailBuilderWithRecipientAndMessage}
   */
  msg (message)   { return new EmailBuilderWithRecipientAndMessage(this, {message: message}); }
}

/** @final
 *  @extends EmailBuilder */
class EmailBuilderWithSubject extends EmailBuilder {
  /**
   * @param {!string} sender
   * @returns {!EmailBuilderWithSenderAndSubject}
   */
  from(sender)    { return new EmailBuilderWithSenderAndSubject(   this, {sender:    sender}   ); }

  /**
   * @param {!string} recipient
   * @returns {!EmailBuilderWithRecipientAndSubject}
   */
  to  (recipient) { return new EmailBuilderWithRecipientAndSubject(this, {recipient: recipient}); }

  /**
   * @param {!string} message
   * @returns {!EmailBuilderWithSubjectAndMessage}
   */
  msg (message)   { return new EmailBuilderWithSubjectAndMessage(  this, {message:   message}  ); }
}

/** @final
 *  @extends EmailBuilder */
class EmailBuilderWithMessage extends EmailBuilder {
  /**
   * @param {!string} sender
   * @returns {!EmailBuilderWithSenderAndMessage}
   */
  from(sender)    { return new EmailBuilderWithSenderAndMessage(   this, {sender:    sender}   ); }

  /**
   * @param {!string} recipient
   * @returns {!EmailBuilderWithRecipientAndMessage}
   */
  to  (recipient) { return new EmailBuilderWithRecipientAndMessage(this, {recipient: recipient}); }

  /**
   * @param {!string} subject
   * @returns {!EmailBuilderWithSubjectAndMessage}
   */
  re  (subject)   { return new EmailBuilderWithSubjectAndMessage(  this, {subject:   subject}  ); }
}

/** @final
 *  @extends EmailBuilder */
class EmailBuilderWithSenderAndRecipient extends EmailBuilder {
  /**
   * @param {!string} subject
   * @returns {!EmailBuilderWithSenderAndRecipientAndSubject}
   */
  re  (subject)   { return new EmailBuilderWithSenderAndRecipientAndSubject(this, {subject: subject}); }

  /**
   * @param {!string} message
   * @returns {!EmailBuilderWithSenderAndRecipientAndMessage}
   */
  msg (message)   { return new EmailBuilderWithSenderAndRecipientAndMessage(this, {message: message}); }
}

/** @final
 *  @extends EmailBuilder */
class EmailBuilderWithSenderAndSubject extends EmailBuilder {
  /**
   * @param {!string} recipient
   * @returns {!EmailBuilderWithSenderAndRecipientAndSubject}
   */
  to  (recipient) { return new EmailBuilderWithSenderAndRecipientAndSubject(this, {recipient: recipient}); }

  /**
   * @param {!string} message
   * @returns {!EmailBuilderWithSenderAndSubjectAndMessage}
   */
  msg (message)   { return new EmailBuilderWithSenderAndSubjectAndMessage(  this, {message:   message}  ); }
}

/** @final
 *  @extends EmailBuilder */
class EmailBuilderWithSenderAndMessage extends EmailBuilder {
  /**
   * @param {!string} recipient
   * @returns {!EmailBuilderWithSenderAndRecipientAndMessage}
   */
  to  (recipient) { return new EmailBuilderWithSenderAndRecipientAndMessage(this, {recipient: recipient}); }

  /**
   * @param {!string} subject
   * @returns {!EmailBuilderWithSenderAndSubjectAndMessage}
   */
  re  (subject)   { return new EmailBuilderWithSenderAndSubjectAndMessage(  this, {subject:   subject}  ); }
}

/** @final
 *  @extends EmailBuilder */
class EmailBuilderWithRecipientAndSubject extends EmailBuilder {
  /**
   * @param {!string} sender
   * @returns {!EmailBuilderWithSenderAndRecipientAndSubject}
   */
  from(sender)    { return new EmailBuilderWithSenderAndRecipientAndSubject( this, {sender:  sender} ); }

  /**
   * @param {!string} message
   * @returns {!EmailBuilderWithRecipientAndSubjectAndMessage}
   */
  msg (message)   { return new EmailBuilderWithRecipientAndSubjectAndMessage(this, {message: message}); }
}

/** @final
 *  @extends EmailBuilder */
class EmailBuilderWithRecipientAndMessage extends EmailBuilder {
  /**
   * @param {!string} sender
   * @returns {!EmailBuilderWithSenderAndRecipientAndMessage}
   */
  from(sender)    { return new EmailBuilderWithSenderAndRecipientAndMessage( this, {sender:  sender} ); }

  /**
   * @param {!string} subject
   * @returns {!EmailBuilderWithRecipientAndSubjectAndMessage}
   */
  re  (subject)   { return new EmailBuilderWithRecipientAndSubjectAndMessage(this, {subject: subject}); }
}

/** @final
 *  @extends EmailBuilder */
class EmailBuilderWithSubjectAndMessage extends EmailBuilder {
  /**
   * @param {!string} sender
   * @returns {!EmailBuilderWithSenderAndSubjectAndMessage}
   */
  from(sender)    { return new EmailBuilderWithSenderAndSubjectAndMessage(   this, {sender:    sender   }); }

  /**
   * @param {!string} recipient
   * @returns {!EmailBuilderWithRecipientAndSubjectAndMessage}
   */
  to  (recipient) { return new EmailBuilderWithRecipientAndSubjectAndMessage(this, {recipient: recipient}); }
}

/** @final
 *  @extends EmailBuilder */
class EmailBuilderWithSenderAndRecipientAndSubject extends EmailBuilder {
  /**
   * @param {!string} message
   * @returns {!Email}
   */
  msg (message)   { return new Email(this, {message: message}); }
}

/** @final
 *  @extends EmailBuilder */
class EmailBuilderWithSenderAndRecipientAndMessage extends EmailBuilder {
  /**
   * @param {!string} subject
   * @returns {!Email}
   */
  re  (subject)   { return new Email(this, {subject: subject}); }
}

/** @final
 *  @extends EmailBuilder */
class EmailBuilderWithSenderAndSubjectAndMessage extends EmailBuilder {
  /**
   * @param {!string} recipient
   * @returns {!Email}
   */
  to  (recipient) { return new Email(this, {recipient: recipient}); }
}

/** @final
 *  @extends EmailBuilder */
class EmailBuilderWithRecipientAndSubjectAndMessage extends EmailBuilder {
  /**
   * @param {!string} sender
   * @returns {!Email}
   */
  from(sender)    { return new Email(this, {sender:  sender}); }
}

/** @final
 *  @extends EmailBuilder */
class Email extends EmailBuilder {
  send() { console.log(`Sending this email: ${this}`); }
}

If you define it like that, you get type checking and useful autocompletion, e.g. typing

mail().

will display from, to, re, and msg, but not send, whereas typing

mail().re("Something").from("Something").

will only display to and msg, and

mail().from("").to("").re("").msg("").

will only display send.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.