Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Best Practices for Long Constructors in JavaScript

I'm creating objects with lots of properties and I'm curious about best practices for instantiating them. It seems like it's pretty bad to have really long constructors (instantiating new objects is not fun).

function Book(title, author, pages, chapters, publisher, datePublished, authorHometown, protagonistFavoriteColor) {
  this.title = title;
  this.authorpages = authorpages;
  this.pages = pages;
  this.chapters = chapters;
  this.publisher = publisher;
  this.datePublished = datePublished;
  this.authorHometown = authorHometown;
  this.protagonistFavoriteColor = protagonistFavoriteColor;
}

// not reliable to remember how to order params
var rc = new Book("Robinson Crusoe", "Daniel Defoe", 342, 16, ...);

I'm wondering if perhaps I should just set maybe three important properties in the constructor (e.g.title, author, and pages) and write individual setters for the rest. Or for consistency should I only use setters? If setting this way is the best path to follow, is there a good way in JS to mandate that those methods be called (kind of like interfaces in Java)?

function Book (title, author, pages){
  this.title = title;
  this.author = author;
  this.pages = pages;
  this.chapters = null;
  this.publisher = null;
  this.datePublished = null;
  this.authorHometown = null;
  this.protagonistFavoriteColor = null;
}

var rc = new Book("Robinson Crusoe", "Daniel Defoe", 342);
rc.setChapters(16);
rc.setPublisher("John Smith Co.");
rc.setDatePublished("04-25-1719");
rc.setAuthorHometown("London");
rc.setProtagonistFavoriteColor("lilac");
// we'd also want to mandate that these setters be called so nothing is left null

Lastly, would passing in an object to my constructor and destructuring it total defeat the pt of a constructor?

like image 242
Jeremy Avatar asked Apr 17 '17 18:04

Jeremy


3 Answers

Best practice would be to pass an object defining properties into constructor:

function Book(props) {
  // create variables out of object (if you need to)
  const {
    title,
    author,
    pages,
    chapters,
    publisher,
    datePublished,
    authorHometown,
    protagonistFavoriteColor
  } = props;

  // assign properties to instance object
  Object.assign(this, props);
}

const rc = new Book({
  title: "Robinson Crusoe",
  author: "Daniel Defoe",
  pages: 342,
  chapters: 16,
  // rest of properties
});

console.log(rc);

JSFiddle Demo: https://jsfiddle.net/Lr6umykn/3/

like image 131
Miguel Mota Avatar answered Oct 30 '22 18:10

Miguel Mota


Seems like it might be best to use an arguments object and a mixin. This is kind of a double edged sword in that it makes the code that instantiates the object easier to read, but the constructor itself a little less obvious. e.g.

function Book(args) {
     Object.assign(this, args);
}

var rc = new Book({
    name:   "Robinson Crusoe",
    author: "Daniel Defoe",
    pages:  342
});

If you want default values, then you can implement that with another mixin e.g.

function Book(args) {
    args = Object.assign(args, {
       protagonistFavoriteColor: "Red"
    });

    Object.assign(this, args);
 }

Then an invocation such as :

var rc = new Book({
    name:   "Robinson Crusoe",
    author: "Daniel Defoe",
    pages:  342
});

Would give :

rc.author; // "Daniel Defoe"
rc.protagonistFavoriteColor // "Red"

If you want to make sure certain values are provided, you would need to test at the end of the constructor that those are present and throw an Error.

like image 26
Woody Avatar answered Oct 30 '22 16:10

Woody


In es6 you can use destructuring and Object.assign to simplify the copy constructor pattern (a constructor taking a parameter-laden object as its single argument):

function Book({title, author, pages, chapters, publisher, datePublished,
               authorHometown, protagonistFavoriteColor}) {
  Object.assign(this, {title, author, pages, chapters, publisher, datePublished,
                       authorHometown, protagonistFavoriteColor});
}

var rc = new Book({title: "Robinson Crusoe", author: "Daniel Defoe",
                   pages: 342, chapters: 16});

var copy = new Book(rc);

console.log(JSON.stringify(rc));
console.log(JSON.stringify(copy));
console.log(copy == rc); // false

It's called that, because you can now conveniently create an object from another instance.

We enumerate each property in Object.assign, to assign only valid parameters.

Does this defeat the purpose of having a constructor in the first place? If this is all you class does, then yes. Yes it does. But hopefully your class has some methods and purpose to it other than this.

like image 31
jib Avatar answered Oct 30 '22 17:10

jib