DRYing a stateless functional builder This was just an experiment to see if it was possible to build a stateless builder in ES6. I was able to do it, but when I took it a step further I got stuck trying to DRY it out.

In this case, we have a MoveableBuilder and a ParticleBuilder. A Particle is a Moveable, so the ParticleBuilder could reuse a lot of the code from the MoveableBuilder. Unfortunately, I haven't been able to figure out how to combine the two functions in an elegant way.



function ParticleBuilder(coordinates = [], vector = [], lifespan = 255, decay = 1) {
  const self = this;
  
  this.at = (...coordinates) =&gt; 
    new self.constructor(coordinates, vector, lifespan, decay);
  
  this.facing = (...vector) =&gt; 
    new self.constructor(coordinates, vector, lifespan, decay);
  
  this.lifespan = (lifespan) =&gt; 
    new self.constructor(coordinates, vector, lifespan, decay);
  
  this.decay = (decay) =&gt; 
    new self.constructor(coordinates, vector, lifespan, decay);
  
  this.build = () =&gt; Object.freeze({
    coordinates: coordinates,
    vector: vector,
    lifespan: lifespan,
    decay: decay
  });
}

function MoveableBuilder(coordinates = [], vector = []) {
  const self = this;
  
  this.at = (...coordinates) =&gt; new self.constructor(coordinates, vector);
  
  this.facing = (...vector) =&gt; new self.constructor(coordinates, vector);
  
  this.build = () =&gt; Object.freeze({
    coordinates: coordinates,
    vector: vector
  });
}

console.info(new MoveableBuilder().at(1,1).facing(2,1).build());

console.info(new ParticleBuilder().at(1, 1).build());




What I had trouble with is how to reuse the at and facing methods within the ParticleBuilder. Granted, since the signatures of both builders is different, it might not be the correct thing to do. However, I figured I could elicit the community for feedback on this.
Found a solution and I think this is elegant, but I'm unsure if this is something that should be avoided or not since I'm fairly new to ES6.

Here are the following things I did to refactor it:


Unify the Builder signatures to use a single parameter product
Invoke the parent constructor within the child constructor with the same arguments on the instance.
Move the defaults into the build step of the Builder.
Use Object.assign to merge new properties.


There's a bit more that can be done to clean this up even further, but hopefully, this could be useful for someone else. One could repeat step 2 to include multiple Builders as well.



function ParticleBuilder(product) {
  MoveableBuilder.call(this, product);
  this.lifespan = (lifespan) =&gt;
    new this.constructor(Object.assign({}, product, {
      lifespan: lifespan
    }));

  this.decay = (decay) =&gt;
    new this.constructor(Object.assign({}, product, {
      decay: decay
    }));

  this.build = () =&gt; Object.assign({
      lifespan: 255,
      decay: 1
    },
    product);
};

function MoveableBuilder(product) {
  this.at = (...coordinates) =&gt;
    new this.constructor(Object.assign({}, product, {
      coordinates: coordinates
    }));

  this.facing = (...vector) =&gt;
    new this.constructor(Object.assign({}, product, {
      vector: vector
    }));

  this.build = () =&gt; Object.assign({
    coordinates: [],
    vector: []
  }, product);
}

console.info(new MoveableBuilder().at(1, 1).facing(2, 1).build());

console.info(new ParticleBuilder().at(1, 1).build());