11

I'm playing around with ES6 classes and my eventual goal is to understand the difference between classes vs constructor functions vs factory functions. My current understanding is that constructor functions and classes are basically using the same design pattern, classes just being the new syntax for the constructor functions. Based on this assumption, I'm trying to create some examples that display the contrast between class/constructor functions and factory functions.

With the examples below I aim to return new objects and some methods that are inherited.

My first example is rather straightforward with the class syntax.

Example #1: Classes

class Person {
    constructor(name, age, location, occupation) {
        this.name = name;
        this.age = age;
        this.location = location;
        this.occupation = occupation;
    }
    printDescription() {
        console.log(`My name is ${this.name} and I'm ${this.age} years old. I live in ${this.location} and I work as a ${this.occupation}.`);
    }
}
const firstUser = new Person('Tom', 30, 'Sydney', 'Teacher');
firstUser.printDescription();

With the second example, I'm trying to replicate the first one with a different approach but I'm not sure if this is factory constructor or a factory function.

Example #2: ?

function PersonMaker (name, age, location, occupation) {

    let person = {name, age, location, occupation};

    person.printDetails = () => {
        console.log(`My name is ${name} and I'm ${age} years old. I live in ${location} and I work as a ${occupation}.`);
    };

    return person;
}

const secondUser = PersonMaker('Johnny', 25, 'London', 'Driver');
secondUser.printDetails();

I need to clarify the pattern used in the second example and if it's not a factory function, how I could turn it into one?

cinnaroll45
  • 2,661
  • 7
  • 24
  • 41

3 Answers3

7

Since it returns an object its a factory function - it's already explained there.

Constuctor functions behaviour different from this, it doesn't return value:

function Person(name, age, location, occupation){
    this.name = name
    this.age = age
    this.location = location
    this.occupation = occupation
}

Person.prototype.printDetails = function(){
        console.log(`My name is ${this.name} and I'm ${this.age} years old. I live in ${this.location} and I work as a ${this.occupation}.`);
};

const secondUser = new Person('Johnny', 25, 'London', 'Driver');
secondUser.printDetails();

I used definition of methods by extending prototype just to separate constructor function, and you can still define a methods inside constructor function as well:

function Person(name, age, location, occupation){
    this.name = name
    this.age = age
    this.location = location
    this.occupation = occupation

    this.printDetails = function(){
        console.log(`My name is ${this.name} and I'm ${this.age} years old. I live in ${this.location} and I work as a ${this.occupation}.`);
    };
}

const secondUser = new Person('Johnny', 25, 'London', 'Driver');
secondUser.printDetails();
Community
  • 1
  • 1
Panama Prophet
  • 1,027
  • 6
  • 6
  • Ok, so I was able to create a factory function with this example after all. How about this part? _My current understanding is that constructor functions and classes are basically using the same design pattern, classes just being the new syntax for the constructor functions._ Do you consider this Is it a valid explanation? – cinnaroll45 Jan 15 '17 at 09:04
  • 1
    Yes, it's a right understanding, classes is just a new syntactic sugar in es6 – Panama Prophet Jan 15 '17 at 09:06
  • 1
    Just note that the `class` syntax is more restrictive in ES6: you cannot define private members. – trincot Jan 15 '17 at 09:08
  • Really? `function Test(){ var a = 'test'; Object.defineProperty(this, 'b', { get(){ return a + 5 }, set(value){ a = value - 5 }}) }` – Panama Prophet Jan 15 '17 at 09:14
  • I just realized that your example was demonstrating a constructor function. At first I thought you were just quoting my factory function example, then I noticed prototype and new is being used. Everything is clear now. You could maybe edit your answer clarifying before the code block along the lines of _'here's a constructor example:'._ – cinnaroll45 Jan 15 '17 at 09:20
  • @trincot oh, sorry, of course you right - no syntax sugar for that still – Panama Prophet Jan 15 '17 at 09:23
4

Javascript takes its roots in Prototypal Inheritance:

// Prototype
var ProtoCtr = function ProtoCtr(name, age) {
    this.name = name;
    this.age = age;
};
ProtoCtr.prototype.printDetails = function printDetails() { console.log(`Hi, I'm ${this.name} and I'm ${this.age} years old`); };

var protoInstance = new ProtoCtr('John', 21);
protoInstance.printDetails();

In short:

  • Functions expose prototypes

  • Defines a this context referring to the instance

  • Use the new keyword to create an instance

Then, with ES6, the language offered the possibility to use the class keyword (to please classical OO developers). It's a syntax which allow to group the declaration of your object in a block, avoiding to extend the prototype "manually". Under the hood, it does the exact same thing than the prototypal model, it's just an other way to write it.

// Class
class ClassCtr {
    constructor(name, age) {
        this.name = name;
        this.age = age;
    }
    printDetails() {
        console.log(`Hi, I'm ${this.name} and I'm ${this.age} years old`);
    }
}
var classInstance = new ClassCtr('John', 21);
classInstance.printDetails();

Key concepts are about the same, but you don't use prototype to expose methods to your instance but declare it directly inside the class block.

Then, there is the factory pattern, which is not a language specs but a pattern, a way to do things. It's my personal favorite. The factory function builds and return the instance. With this method, you get rid of the new keyword and don't need the this anymore to refer to you instance:

// Factory
var factoryCtr = function factoryCtr(name, age) {
    var instance = {
        name: name,
        age: age,
        printDetails: function printDetails() { console.log(`Hi, I'm ${instance.name} and I'm ${instance.age} years old`); }
    };
    return instance;
}
var factoryInstance = factoryCtr('John', 21);
factoryInstance.printDetails();

Even if Class is not covered by this talk, I recommand you to watch this video: https://www.youtube.com/watch?v=ya4UHuXNygM

I hope this helped :)

cinnaroll45
  • 2,661
  • 7
  • 24
  • 41
Booster2ooo
  • 1,373
  • 9
  • 11
  • Great examples! Your factory function example also invoked another question I had in mind. I noticed that you only passed two properties (name, age) to your instance where as I did four (name, age, location, occupation). Would passing plenty of arguments like I did be considered a bad practice? – cinnaroll45 Jan 15 '17 at 09:29
  • 1
    As far as I know, it's just a matter of preferences. IMO, it's "more obvious" for someone reading your code to pass the arguments one by one: function (name, age, location, ...). The dev will see in one look what he have to pass. Still, I like to pass a "payload" containing everything: function(options) { // check options.age, options.name ... }. The last thing you can do is use the arguments `super object` but this is considered as a bad practice afaik. – Booster2ooo Jan 15 '17 at 09:34
  • 3
    The problem with your factory example is lets say you had to generate a large quantity of objects, the factory would take at-least 50-70% longer than a constructor. Moreover, each object you create would use unnecessary amounts of memory because each object would have memory allocated for the `printDetails` method. One of the core uses of prototypal inheritance is to avoid this in the first place. Create a single function and add it to to the prototype of those objects that require access to it. – Cazineer Apr 14 '18 at 05:01
4

To avoid passing many arguments, you can do it like this.

const PersonMaker = description => {
  const {name, age, location, occupation} = description;
  return {
    printDetails: () => {
      console.log(
        `My name is ${name} and I'm ${age} years old. I live in ${location} and I work as a ${occupation}.`,
      );
    },
  };
};

const secondUser = PersonMaker({
  name: 'Johnny',
  age: '25',
  location: 'London',
  occupation: 'Driver',
});


console.log(secondUser.printDetails());
Polisas
  • 491
  • 1
  • 9
  • 20