DEV Community

Cover image for 4 Dangerous Problems in JavaScript Easily Solved by The Builder Design Pattern
jsmanifest
jsmanifest

Posted on • Originally published at jsmanifest.com

4 Dangerous Problems in JavaScript Easily Solved by The Builder Design Pattern

Find me on medium

When you're developing apps in JavaScript you sometimes find it difficult to construct objects that are complex. Once it hits this certain point in your code, it becomes more important as it can get way more complex as your app gets larger.

The "complex"ity can come in several forms. One could be that your code gets repetitive when you're trying to create different variations of certain objects. Another one could be that attempting to create those variations of objects can become quite long because you'd be having to do the logic in one giant block somewhere, like during the constructor block of a class.

This article will go over these problems and will show how the Builder Design Pattern in JavaScript will make those problems much less of an issue.

So what are the problems that the Builder pattern can easily solve?

Let's first look at an example without the builder pattern, and then an example with the builder pattern so that i'm not the only one with a visual code example in mind as we're going along:

In the following code examples, we're defining a Frog class. We'll pretend that in order for the Frog class to be fully capable of living and venturing out in the wild without a problem, they would require two eyes, all four legs, a scent, a tongue, and a heart. Now obviously in the real world there's a lot more involved and it sounds ridiculous to require a scent to be able to live, but we'll just keep it both simple and interesting rather than being fully factual about everything. We can get our facts 100% correct in another post at another time :)

Without the builder pattern

class Frog {
  constructor(name, gender, eyes, legs, scent, tongue, heart, weight, height) {
    this.name = name
    this.gender = gender
    this.eyes = eyes
    this.legs = legs
    this.scent = scent
    this.tongue = tongue
    this.heart = heart
    if (weight) {
      this.weight = weight
    }
    if (height) {
      this.height = height
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

With the builder pattern

class FrogBuilder {
  constructor(name, gender) {
    this.name = name
    this.gender = gender
  }

  setEyes(eyes) {
    this.eyes = eyes
    return this
  }

  setLegs(legs) {
    this.legs = legs
    return this
  }

  setScent(scent) {
    this.scent = scent
    return this
  }

  setTongue(tongue) {
    this.tongue = tongue
    return this
  }

  setHeart(heart) {
    this.heart = heart
    return this
  }

  setWeight(weight) {
    this.weight = weight
    return this
  }

  setHeight(height) {
    this.height = height
    return this
  }
}
Enter fullscreen mode Exit fullscreen mode

Now this seems a little overkill because the builder pattern example is larger in code. But if you dig deeper into all the cases that would occur during the development of a potential frog application, you will see that by looking at these two examples, the code example with the builder pattern applied will slowly rise in promoting simplicity, maintainability, and opening more opportunities to implement robust functionality.

Here are the 4 problems that the Builder Design Pattern can easily solve in JavaScript:

1. Code clutter and confusion

It's not uncommon that errors and accidents occur from carelessness of developing in large sizes of function blocks. In addition, when there are too many things going on in a single block, it is easy to get confused.

So what what kind of situation would you get into when there are "too many things going on" in function blocks, like the constructor?

Going back at our first code example implemented without the builder pattern, lets assume we have to add in some additional logic in order to accept the passed in arguments before applying them into an instance:

class Frog {
  constructor(name, gender, eyes, legs, scent, tongue, heart, weight, height) {
    if (!Array.isArray(legs)) {
      throw new Error('Parameter "legs" is not an array')
    }
    // Ensure that the first character is always capitalized
    this.name = name.charAt(0).toUpperCase() + name.slice(1)
    this.gender = gender
    // We are allowing the caller to pass in an array where the first index is the left eye and the 2nd is the right
    //    This is for convenience to make it easier for them.
    //    Or they can just pass in the eyes using the correct format if they want to
    //    We must transform it into the object format if they chose the array approach
    //      because some internal API uses this format
    this.eyes = Array.isArray(eyes) ? { left: eye[0], right: eye[1] } : eyes
    this.legs = legs
    this.scent = scent
    // Pretending some internal API changed the field name of the frog's tongue from "tongueWidth" to "width"
    //    Check for old implementation and migrate them to the new field name
    const isOld = 'tongueWidth' in tongue
    if (isOld) {
      const newTongue = { ...tongue }
      delete newTongue['tongueWidth']
      newTongue.width = tongue.width
      this.tongue = newTongue
    } else {
      this.tongue = newTongue
    }
    this.heart = heart
    if (typeof weight !== 'undefined') {
      this.weight = weight
    }
    if (typeof height !== 'undefined') {
      this.height = height
    }
  }
}

const larry = new Frog(
  'larry',
  'male',
  [{ volume: 1.1 }, { volume: 1.12 }],
  [{ size: 'small' }, { size: 'small' }, { size: 'small' }, { size: 'small' }],
  'sweaty socks',
  { tongueWidth: 18, color: 'dark red', type: 'round' },
  { rate: 22 },
  6,
  3.5,
)
Enter fullscreen mode Exit fullscreen mode

Our constructor is a little long, and in some cases it doesn't even seem like a lot of the logic won't even be necessary. It is cluttered by logic of handling different parameters. This can be confusing especially if we haven't looked at the source code of this in a long time.

When we're developing a frog application and we want to instantiate an instance of a Frog, the downside is that we would have to make sure that we get every parameter near 100% perfect in terms of following the function signature or something will throw during the construction phase. If we need to double check the type of eyes at some point, we would have to scan through the clutter of code to get to the code we're looking for. Would you start being confused if you finally found the lines you were looking for, but then realized there was another line of code that was referencing and affecting the same parameter just 50 lines above? Now you have to go back and scan through those to be able to understand what will happen.

If we take another look at the FrogBuilder constructor from an earlier example, we're able to simplify the constructor to feel more "natural" while removing the confusion. We would still be doing the extra validations, it would just be isolated into their own little methods, which is the heart and soul of the builder pattern.

2. Readability

If we take a look at the most recent code example, it's already getting a little hard to read because we have to process these different variations of handling at once. There's no way around it than to understand the whole thing at once if we wanted to create instances of a Frog.

In addition, we have to provide some documentation otherwise we'd be unsure why in the world is tongueWidth being renamed to width. This is absurd!

If we convert the example to use the builder pattern, we can make things more easily readable:

class FrogBuilder {
  constructor(name, gender) {
    // Ensure that the first character is always capitalized
    this.name = name.charAt(0).toUpperCase() + name.slice(1)
    this.gender = gender
  }

  formatEyesCorrectly(eyes) {
    return Array.isArray(eyes) ? { left: eye[0], right: eye[1] } : eyes
  }

  setEyes(eyes) {
    this.eyes = this.formatEyes(eyes)
    return this
  }

  setLegs(legs) {
    if (!Array.isArray(legs)) {
      throw new Error('"legs" is not an array')
    }
    this.legs = legs
    return this
  }

  setScent(scent) {
    this.scent = scent
    return this
  }

  updateTongueWidthFieldName(tongue) {
    const newTongue = { ...tongue }
    delete newTongue['tongueWidth']
    newTongue.width = tongue.width
    return newTongue
  }

  setTongue(tongue) {
    const isOld = 'tongueWidth' in tongue
    this.tongue = isOld
      ? this.updateTongueWidthFieldName(tongue, tongue.tongueWidth)
      : tongue
    return this
  }

  setHeart(heart) {
    this.heart = heart
    return this
  }

  setWeight(weight) {
    if (typeof weight !== 'undefined') {
      this.weight = weight
    }
    return this
  }

  setHeight(height) {
    if (typeof height !== 'undefined') {
      this.height = height
    }
    return this
  }

  build() {
    return new Frog(
      this.name,
      this.gender,
      this.eyes,
      this.legs,
      this.scent,
      this.tongue,
      this.heart,
      this.weight,
      this.height,
    )
  }
}

const larry = new FrogBuilder('larry', 'male')
  .setEyes([{ volume: 1.1 }, { volume: 1.12 }])
  .setScent('sweaty socks')
  .setHeart({ rate: 22 })
  .setWeight(6)
  .setHeight(3.5)
  .setLegs([
    { size: 'small' },
    { size: 'small' },
    { size: 'small' },
    { size: 'small' },
  ])
  .setTongue({ tongueWidth: 18, color: 'dark red', type: 'round' })
  .build()
Enter fullscreen mode Exit fullscreen mode

We gained the ability to make our code much more readable in a couple of ways:

  1. The names of the methods are sufficiently self-documenting
  • updateTongueWidthFieldName easily defines to us what it does and why it's doing it. We know that its updating the field name. And we also know why because the word "update" already means to bring up to date! This self-documented code helps us assume that some field name is old and needs to be changed to use the new field name.
  1. The constructor is short and simplified.
  • It's perfectly fine to set the other properties later!
  1. Can clearly understand each parameter when initiating a new Frog
  • It's like reading English. You're clearly setting the eyes, legs, etc and finally invoking the build method to create a Frog.
  1. Each logic is now isolated in separate blocks where we can easily follow through
  • When you're doing some changes you only need to focus on one thing, which is what ever that got isolated in function blocks.

3. Lack of control

The most important one on this list is benefiting from more control over the implementation. Prior to the builder example, it is possible to write more code in the constructor, but the more code you try to stick in there the more it degrades readability which causes clutter and confusion.

Since we're able to isolate implementation details to each of their own function blocks, we now have finer control in many ways.

One way is that we can add validations without even adding more problems, which makes the construction phase more robust:

setHeart(heart) {
  if (typeof heart !== 'object') {
    throw new Error('heart is not an object')
  }
  if (!('rate' in heart)) {
    throw new Error('rate in heart is undefined')
  }
  // Assume the caller wants to pass in a callback to receive the current frog's weight and height that he or she has set
  //    previously so they can calculate the heart object on the fly. Useful for loops of collections
  if (typeof heart === 'function') {
    this.heart = heart({
      weight: this.weight,
      height: this.height
    })
  } else {
    this.heart = heart
  }

  return this
}

validate() {
  const requiredFields = ['name', 'gender', 'eyes', 'legs', 'scent', 'tongue', 'heart']
  for (let index = 0; index < requiredFields.length; index++) {
    const field = requiredFields[index]
    // Immediately return false since we are missing a parameter
    if (!(field in this)) {
      return false
    }
  }
  return true
}

build() {
  const isValid = this.validate(this)
  if (isValid) {
  return new Frog(
    this.name,
    this.gender,
    this.eyes,
    this.legs,
    this.scent,
    this.tongue,
    this.heart,
    this.weight,
    this.height,
  )
  } else {
    // just going to log to console
    console.error('Parameters are invalid')
  }
}
Enter fullscreen mode Exit fullscreen mode

We took advantage of the fact that each part of the constructor is isolated by adding in validations as well as a validate method to ensure that all of the required fields have been set before finally building the Frog.

We can also take advantage of these opened opportunities to add further custom input data types to build the original return value of a parameter.

For example we can add more custom ways the caller can pass in eyes, to provide them even more convenience than what we previously provided:

formatEyesCorrectly(eyes) {
  // Assume the caller wants to pass in an array where the first index is the left
  //    eye, and the 2nd is the right
  if (Array.isArray(eyes)) {
    return {
      left: eye[0],
      right: eye[1]
    }
  }
  // Assume that the caller wants to use a number to indicate that both eyes have the exact same volume
  if (typeof eyes === 'number') {
    return {
      left: { volume: eyes },
      right: { volume: eyes },
    }
  }
  // Assume that the caller might be unsure of what to set the eyes at this current moment, so he expects
  //    the current instance as arguments to their callback handler so they can calculate the eyes by themselves
  if (typeof eyes === 'function') {
    return eyes(this)
  }

    // Assume the caller is passing in the directly formatted object if the code gets here
  return eyes
}

setEyes(eyes) {
  this.eyes = this.formatEyes(eyes)
  return this
}
Enter fullscreen mode Exit fullscreen mode

This way it makes it easier for the caller to choose any variation of input types they want:

// variation 1 (left eye = index 1, right eye = index 2)
larry.setEyes([{ volume: 1 }, { volume: 1.2 }])

// variation 2 (left eye + right eye = same values)
larry.setEyes(1.1)

// variation 3 (the caller calls the shots on calculating the left and right eyes)
larry.setEyes(function(instance) {
  let leftEye, rightEye
  let weight, height
  if ('weight' in instance) {
    weight = instance.weight
  }
  if ('height' in instance) {
    height = instance.height
  }

  if (weight > 10) {
    // It's a fat frog. Their eyes are probably humongous!
    leftEye = { volume: 5 }
    rightEye = { volume: 5 }
  } else {
    const volume = someApi.getVolume(weight, height)
    leftEye = { volume }
    // Assuming that female frogs have shorter right eyes for some odd reason
    rightEye = { volume: instance.gender === 'female' ? 0.8 : 1 }
  }

  return {
    left: leftEye,
    right: rightEye,
  }
})

// variation 4 (caller decides to use the formatted object directly)
larry.setEyes({
  left: { volume: 1.5 },
  right: { volume: 1.51 },
})
Enter fullscreen mode Exit fullscreen mode

4. Boilerplate (Solved by: Templating)

One concern we might come across in the future is that we end up with some repetitive code.

For example, looking back at our Frog class, do you think that when we want to create certain types of frogs, some of them might have the same exact traits?

In a real world scenario, there are different variations of frogs. A toad for example is a type of a frog, but not all frogs are toads. So that tells us that there are some distinctive properties of a toad that should not belong to normal frogs.

One difference between toads and frogs is that toads spend most of their time on land as opposed to normal frogs who spend most of their time inside water. In addition, toads also have dry bumpy skin whereas the skin of normal frogs are a little slimy.

That means we're going to have to ensure some how that every time a frog is instantiated, only some values can make it through as well as some values must make it through.

Let's go back to our Frog constructor and add in two new parameters: habitat, and skin:

class Frog {
  constructor(
    name,
    gender,
    eyes,
    legs,
    scent,
    tongue,
    heart,
    habitat,
    skin,
    weight,
    height,
  ) {
    this.name = name
    this.gender = gender
    this.eyes = eyes
    this.legs = legs
    this.scent = scent
    this.tongue = tongue
    this.heart = heart
    this.habitat = habitat
    this.skin = skin
    if (weight) {
      this.weight = weight
    }
    if (height) {
      this.height = height
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Making two simple changes to this constructor was already a little confusing! This is why the builder pattern is recommended. If we put the habitat and skin parameters at the end, it might cause bugs because weight and height can possibly be undefined since they are both optional! And since they are optional, if the caller doesn't pass those in, then habitat and skin will mistakenly be used for them. Yikes!

Lets edit the FrogBuilder to support habitat and skin:

setHabitat(habitat) {
  this.habitat = habitat
}

setSkin(skin) {
  this.skin = skin
}

Enter fullscreen mode Exit fullscreen mode

Lets now pretend we need to create 2 separate toads and 1 normal frog:

// frog
const sally = new FrogBuilder('sally', 'female')
  .setEyes([{ volume: 1.1 }, { volume: 1.12 }])
  .setScent('blueberry')
  .setHeart({ rate: 12 })
  .setWeight(5)
  .setHeight(3.1)
  .setLegs([
    { size: 'small' },
    { size: 'small' },
    { size: 'small' },
    { size: 'small' },
  ])
  .setTongue({ width: 12, color: 'navy blue', type: 'round' })
  .setHabitat('water')
  .setSkin('oily')
  .build()

// toad
const kelly = new FrogBuilder('kelly', 'female')
  .setEyes([{ volume: 1.1 }, { volume: 1.12 }])
  .setScent('black ice')
  .setHeart({ rate: 11 })
  .setWeight(5)
  .setHeight(3.1)
  .setLegs([
    { size: 'small' },
    { size: 'small' },
    { size: 'small' },
    { size: 'small' },
  ])
  .setTongue({ width: 12.5, color: 'olive', type: 'round' })
  .setHabitat('land')
  .setSkin('dry')
  .build()

// toad
const mike = new FrogBuilder('mike', 'male')
  .setEyes([{ volume: 1.1 }, { volume: 1.12 }])
  .setScent('smelly socks')
  .setHeart({ rate: 15 })
  .setWeight(12)
  .setHeight(5.2)
  .setLegs([
    { size: 'medium' },
    { size: 'medium' },
    { size: 'medium' },
    { size: 'medium' },
  ])
  .setTongue({ width: 12.5, color: 'olive', type: 'round' })
  .setHabitat('land')
  .setSkin('dry')
  .build()
Enter fullscreen mode Exit fullscreen mode

So where is the repetitive code in this?

If we look closely, notice we have to repeat the toad's habitat and skin setters. What if there were 5 more setters that are exclusive only to toads? We would have to manually apply this template for toads every time we create them--the same goes for normal frogs.

What we can do is to create a templater, which is normally by convention called the Director.

The Director is responsible for executing steps to create objects--usually where there are some common structures that could be defined beforehand when building the final object, like in this case our toad.

So instead of having to manually set the distinctive properties between toads, we can have the director generate that template for us:

class ToadBuilder {
  constructor(frogBuilder) {
    this.builder = frogBuilder
  }
  createToad() {
    return this.builder.setHabitat('land').setSkin('dry')
  }
}

let mike = new FrogBuilder('mike', 'male')
mike = new ToadBuilder(mike)
  .setEyes([{ volume: 1.1 }, { volume: 1.12 }])
  .setScent('smelly socks')
  .setHeart({ rate: 15 })
  .setWeight(12)
  .setHeight(5.2)
  .setLegs([
    { size: 'medium' },
    { size: 'medium' },
    { size: 'medium' },
    { size: 'medium' },
  ])
  .setTongue({ width: 12.5, color: 'olive', type: 'round' })
  .build()
Enter fullscreen mode Exit fullscreen mode

That way, you avoid implementing the boilerplate that all toads share in common and can focus only on the properties you need. This becomes more useful when there are even more properties exclusive only to toads.

Conclusion

And that concludes the end of this post! I hope you found this to be valuable and look out for more in the future!

Find me on medium

Top comments (10)

Collapse
 
willsmart profile image
willsmart • Edited

Thanks for a great post!
It's a great discussion of why the builder pattern is worthwhile, separating construction details from objects' behaviours once it's been set up.

Adding my 2c worth of JS style advice though (nothing specific to builder patterns btw): let's move away from methods like constructor(name, gender, eyes, legs, scent, tongue, heart, weight, height) outright.

In JS if you have a bag of arguments they're better placed in an argument object. Use multiple arguments when there couldn't be any confusion about which param is which when looking at some code that calls the function (i.e. when there's a natural ordering or when order doesn't matter).
So

addThreeNumbers(a,b,c); // no confusion here
log(a,b,c); // logs in order I'd assume
new Frog(a,b,c,d,e,f,g); // um, Brad, we need to talk about your variable naming, I'm heading out for a breather
new Frog({
  name: a,
  eyes: b,
  heart: c,
  gender: d,
  tongue: e,
  scent: f
}) // Brad, we still need that talk but at least I could figure out your code, btw you forgot the legs.

For the Frog thing I'd use something like:

constructor({name, gender, eyes, legs, scent, tongue, heart, weight=undefined, height=undefined}) {....}

(the =undefined is not required but is a note to callers that these are optional)

This adds a whole two characters and no complexity to the code but comes with big wins:

  • The biggest is that it's much harder to make arg ordering mistakes.
  • Notable mention is that arg labels end up following their values around, allowing for easier calls and robustness against additional details, eg:
function buildBobTheFrog() {
  const frogAttributes = {
    ... defaultFrogAttributes,
    name: "Bob",
    gender: "Non-binary",
    pronouns: "It",
    eyes: "Like summer rain",
    legs: "1.5 (don't ask)"
  }

  if (foo) frogAttributes.heart = "bar"

  // ... other code as you see fit to set up the frog's bits

  // The next line is simply intended to create the frog 
  // -- i.e. it does one simple thing introducing one new detail, the class name 
  // -- so the actual line of code should have one basic detail in it: `Frog`.
  // It shouldn't be expected to understand what attributes a frog expects,
  // -- or intricacies of how they are ordered in that particular piece of code.
 return new Frog(frogAttributes);
}
Collapse
 
wkrueger profile image
wkrueger

Builder pattern should be rebranded to “Java Stockholm Syndrome Pattern”.

Collapse
 
rafaelramblas profile image
Rafael Ramblas

This is the exactly the pattern that i hated the most in PHP and Java, every property of your object you need a get and a set because u can't mutate then directly from the instance of the class.

Collapse
 
deathshadow60 profile image
deathshadow60 • Edited

As someone who started out in assembler over four decades ago, this approach really bothers me just due to the overhead of all the blasted "functions for nothing"

I mean in ASM a HLL function call works out to a push to the stack of the current execution point, push of all the argument values, changing execution point, extending the stack for locals, then on exit de-allocating locals, de-allocating the parameters, and then popping the return address.

I don't even want to THINK about the nightmare an interpreted loosely typed language like JavaScript makes of that... but there's a reason you're better off in production code using a for loop instead of Array.each, as the old way runs circles around it, more so if you get the derpy needlessly and painfully cryptic arrow functions involved.

All these new JS functions with their callbacks, and techniques like this of applying even more methods to the class, just seem to create more code and more execution overhead for little if any real-world benefit. More so when you get into that "return this" so you can daisy-chain them TRASH. It was derpy when the monument to developer ignorance and incompetence that is jQuery did it, and I see little here to change my opinion on that topic. You're just producing more copies of the pointer slowing the code down dramatically and wasting more RAM.

Your first example is card stacked too with bad choices... I mean to check if it's a value or undefined, check for undefined. If there's that many parameters just omit them and use arguments[].

Whilst there could be advantages in clarity on the calling side of things, as well as more versatility in being able to omit values, I'm just not sure the performance drop and added complexity is worth it.

Hence why I like passing JSON-type objects and then using Object.assign to apply it to "this", or using a for-loop if specific validations are needed for some values. Simply add a object as a property of the class that contains your validation methods, then if it exists while looping run it.

As this:

const sally = new FrogBuilder({
    "name" : "sally",
    "gender" : "female",
    "eyes" : [ { "volume" : 1.1 }, { "volume" : 1.12 } ],
    "scent" : "blueberry",
    "heart" : { "rate" : 12 },
    "weight" : 5,
    "height" : 3.1,
    "legs" : [
        { "size" : "small" },
        { "size" : "small" },
        { "size" : "small" },
        { "size" : "small" }
    ],
    "tongue" : { "width" : 12, "color" : "navy blue", "type" : "round" },
    "habitat" : "water",
    "skin" : "oily"
});

Would -- to me at least -- be a far more useful format to deal with, particularly given how often JSON is used for information exchange or storage.

Just Object.assign in the constructor, or for loop to apply validation or applications as needed off a object property containing the handler methods, and be done with it.

Collapse
 
tailcall profile image
Maria Zaitseva

As someone with a C background I can relate to this reasoning.

However in the end of the day, seeing that most resource heavy operations are DOM manipulations and network I/O, the costs for creating an extra builder class seem neglible compares to them.

Besides, big objects like that are created relatively rarely, so the overhead likely won't affect anything too much. And it's always possible to optimise if measurements show that this particular object takes too long to build.

Given the benefits of the builder pattern I can live with all of that. However I agree it's wise to use plain objects where possible, JS is tooled perfectly around them and in many cases its tools are just enough.

Collapse
 
thompcd profile image
Corey Thompson

I wouldn't say the Builder pattern exists solely for legibility purposes. Being able to do things async / transactionally instead of a serial burst is one of the first use cases that comes to mind. Building something with a multi-stage setup is something I do a lot in C# for automating serial port hardware, such as detecting a USB HID is on the tree before attempting to open a port is much easier with a method like this.

It's much easier to do incremental construction rather than a try/catch around a giant constructor and requiring catches for 10 different failure modes in the client. In my experience, a non-builder approach makes the coupling between the caller and constructor very tight in order to accommodate the error handling in these situations.

I first learned ASM also, but I've come to terms that ASM exists in a world apart from higher level languages. I like to use patterns like this to ensure that, from a dev perspective, I can building something with a solid foundation and rely on the compiler to optimize for the machine code. And since this post is on JS and I mentioned compiling, I'll just mention that's why I now use Svelte 3. 😊

Collapse
 
otherdev profile image
DevLearn

I totally felt the same reading this. What a waste of resources for literally no benefit apart from legibility, which is hardly a reason unless you don't care about performance.

Collapse
 
wkrueger profile image
wkrueger

This.

And if you wish stricter organization on a bigger project, just use damn typescript (it will also change your views of OOP, type systems and make you notice how stupid the builder pattern is).

Collapse
 
matteusz profile image
Mateusz Littwin

One difference between those two approaches is:

  • with big constructor - you set all params as required
  • with builder - you don't have to call them all

In normal approach you had full object ready to go, which isn't the same with builder for this example.
Imagine if we always need to create a frog with 10 params (Base Frog for example), is it really better to do it builder way? I think it is not.

To be clear builder is great pattern, but we should always operate on object which is ready to go without extras ;)

Collapse
 
wichopy profile image
Will C.

I could see this being used in a big is project. What are the drawbacks of using an object as your Frog constructor parameter instead of changing the signature each time you want to add a new property?