3

concatenative inheritance works like a composition to me when I look to it at the beginning, but people keep naming it as an inheritance. classes, however, use the prototype to create a prototype chain that connects objects together. the question now is, if both concatenative inheritance and class inheritance do the same thing which one to use? here is an example of both scenarios
concatenative inheritance

function Person(name, address) {
 const _name = name
 const _address = address
 const toString = () => `name: ${this.name}, address: ${this.address}`
 return {
   _name,
   _address,
   toString
 }
}


function Employee(name, address, salary) {
 const getAnnualSalary = () => 12 * salary
 return Object.assign({ getAnnualSalary }, Person(name, address))
}



the class inheritance


class Person {
  constructor(name, address) {
    this.name = name
    this.address = address
  }
  toString() { return `name: ${this.name}, address: ${this.address}` }
}


class Employee extends Person {
  constructor(name, address, salary) {
    super(name, address)
    this.salary = salary
  }

  getAnnualSalary() { return 12 * this.salary }
}
Peter Seliger
  • 11,747
  • 3
  • 28
  • 37
Fadi
  • 203
  • 4
  • 14
  • As you said yourself, they *don't* do the same thing. – Bergi Oct 18 '20 at 10:18
  • 1
    It would help if your examples were more complete (have some methods, include the `Person` code, and actually *attempted* to do the same thing, i.e. have `Employee` take the same arguments and return objects with the same properties) to make them better comparable. – Bergi Oct 18 '20 at 10:20
  • 2
    With the current (factory like) example of `function Employee (name) { ... }` it depends on the implementation of `Person`. But of course, if one does not expect any black magic from `Person`, what the OP calls *concatenative inheritance* is one of the many (mixin style) ways of how an object might get composed. Since, as for now, for any object created by the 1st provided example no automatic prototypal delegation will take place (except for the one of `Object`) it should not even have *inheritance* in its name. – Peter Seliger Oct 18 '20 at 10:23
  • 1
    thanks for the feedback, I mentioned in the question that they are doing the same thing, but @Bergi says that they don't. this makes me more confused. I was hoping for more explanation from Bergi – Fadi Oct 18 '20 at 10:29
  • 1
    @Fadi ... both the processes behind and the results of each of your example, in the very moment of being invoked, are entirely different. – Peter Seliger Oct 18 '20 at 10:31
  • 1
    @PeterSeliger thanks for the answer, you said "concatenative inheritance is one of the many (mixin style) ways of how an object might get composed" does that means concatenative inheritance is a composition and not inheritance? – Fadi Oct 18 '20 at 10:31
  • 1
    @PeterSeliger what do you mean by" entirely different" as I understood from my research that they both use a prototypal chain under the hood, am I right? – Fadi Oct 18 '20 at 10:35
  • 1
    @Fadi I can't give any further explanation until you fix the code snippets. Otherwise, the simple and unsatisfying answer is that one creates a `.salary` and the other doesn't. – Bergi Oct 18 '20 at 10:38
  • 1
    In order to answer all of your Q's from your last comments one needs to know the implementation of `Person` (as @Bergi was already asking for too). – Peter Seliger Oct 18 '20 at 10:39
  • @Bergi thaks again for the feedback, I have updated the post. I hope it is now clearer – Fadi Oct 18 '20 at 10:49
  • @Peter Seliger I have expanded the code, I hope it is clearer now, thanks – Fadi Oct 18 '20 at 10:49
  • @Fadi ... I, btw, do strongly doubt that one ever can use the same implementation of `Person` for both of your examples (which was just proven with your last edit). – Peter Seliger Oct 18 '20 at 10:49
  • @PeterSeliger that is my confusion, what is the difference, and which one to use because I cannot get the idea behind these two approaches – Fadi Oct 18 '20 at 10:51

2 Answers2

5

Concatenative inheritance and prototypal inheritance (in JS, sometimes implemented using the class keyword) are two different approaches to delegation. Delegation is a mechanism whereby an object may acquire some or all of its state and behavior from other objects rather than from class (in the Java sense) definitions.

In JS, the word inheritance has a strong association with delegation to an ancestor object via prototypical inheritance, but (surprisingly) according to some, this is not always the case.

Concatenative inheritance is the process of combining the properties of one or more source objects into a new destination object. Believe it or not, it is the most commonly used form of inheritance in JavaScript.

Also:

In JS, the essence of concatenative inheritance is often masked by the common name “mixins”. Confusingly, “mixins” have other meanings in other languages, and even in some JavaScript libraries. It also has uses that would be confusing to call “mixins”. Because of those reasons, I prefer the more precise term “concatenative inheritance”.

Note that although JavaScript includes a "class syntax" (eg. class foo extends bar {}), it does not have "classical" or class-based inheritance in the usual sense of the word. In JavaScript, inheritance using the class syntax is always achieved via prototypical inheritance. Thus in JavaScript, class-based inheritance is almost entirely syntactic sugar over the original prototypical inheritance model that has always existed in JavaScript since Brendan Eich's first ten-day version of the language.

"Composition" is an overloaded term. Object composition usually involves having one object delegate functionality or state to another object contained within it. Technically it specifically means the lifetime of the sub-object is tied to that of the composite, however, in my experience "composition" is usually used to mean "aggregation", which is composition, but whereby the lifetime of the sub-objects is not controlled by the composite, with them usually being injected via a constructor function. Functional composition, on the other hand, is a pattern of combining programming elements whereby function calls are nested in the form f(g(x)).

You can implement concatenative inheritance by following the object compositional pattern, with objects being supplied to a function for concatenation onto the composite object.

In the following example, instances of p will include the functionality present on the object supplied as ancestor.

function createP(ancestor) {
  const p = { 
    ...ancestor, 
    bar() {} 
  }
  return p
}
const o = { foo() {} }
const p = createP(o) // `p` now has both methods `foo` and `bar`

For prototypical inheritance in JS there is a fixed, language supported mechanism for the dynamic look-up of functionality via the prototype chain.

In prototypical inheritance the inherited functionality sits on a separate object somewhere on the prototype chain. This indirection gives this flavour of inheritance different non-functional characteristics.

For example:

  • It is arguably less clear what functionality is being included via prototypal inheritance, than it is via concatenative inheritance, because functionality can exist anywhere along a (potentialy long) prototype chain. Furthermore, functionality can be added and removed from the prototype chain after object creation.
  • For APIs, prototypical inheritance can be useful to keep functionality off child objects, making them appear simple, while enabling them to conveniently expose functionality located on the prototype chain, in an object oriented fashion. eg. You can trivially call Array#splice directly on every array you create: [].splice(...), even though the splice function is located elsewhere (on Array.prototype).
  • In prototypal inheritance, methods on ancestor objects need to be written in such a way that their target (ie. this) could be another object. Arguably in concatenative inheritance use of this is de-emphasised.
  • In prototypal inheritance there exists an ongoing implicit coupling between the inheritor and the ancestor. This can be a source of bugs and make code harder to reason about.
  • More generally, in concatenative inheritance the built-in JS prototypical inheritance mechanism (encompassing new, this, extends, super, the prototype chain etc) is de-emphasised. There is a line of thought, popularized by Douglas Crockford, that this part of the JavaScript language complicates more often than it simplifies, and that it should be avoided where possible. Concatenative inheritance provides an alternative inheritance mechanism.
  • Concatenative inheritance bypasses some of the ontological constraints of prototypical inheritance. A prototype link implies an "is-a" relationship, whereby in concatenative inheritance no ontological relationship is implied. You can mix together as many objects as you want to get precisely the functionality you need (and nothing more).

Choosing between the two approaches is a matter of subjective preference and style. Both approaches have their place.

Ben Aston
  • 53,718
  • 65
  • 205
  • 331
  • 1
    Thanks a lot, the explanation is more than elegant. and the difference between the two approaches is more clear now, but I would still ask. based on what I read from your answer, the concatenative inheritance is not an inheritance, it is object composition like the composition in Java/C# .... which means it the one should choose this approach whenever it is possible because we should favour composition over inheritance – Fadi Oct 18 '20 at 22:13
  • 1
    Moreover, using a factory function will encapsulate the properties like (name, address..), and remove the complexity of using (this keyword) and dealing with the calling context. the class inheritance, however, makes everything public. Based on that, why should I even bother using class inheritance, in which cases it can be useful ? – Fadi Oct 18 '20 at 22:18
  • 1
    For example, prototypical inheritance can be convenient if you are writing a public API because it keeps the “clutter” of library methods off of user-created instances, while at the same time keeping those methods available for convenient use in an object-oriented fashion. – Ben Aston Oct 18 '20 at 22:26
  • 1
    @Fadi ... *"... Moreover, using a factory function will encapsulate the properties like (name, address..) ..."* ... (not necessarily or) it depends on what you want to achieve. Yes, if you do not want to expose any of this data into public. ... *"... and remove the complexity of using (this keyword) ..."* ... Yes but no, since nobody is forcing you to implement a class constructor in a way that it will create references with just public properties. – Peter Seliger Oct 18 '20 at 22:26
4

The following explanation tries to be brief but comprehensive.

Let's focus first on the different implementations of Person and also let's start with the class based version, for its implementation is clean unlike the in many aspects error prone one of its factory counterpart.

class Person { ... toString() { ... } } features a Person type specific toString method. The latter is implemented as a prototype method of Person. Thus any Person instance like myPerson does not feature its own toString method.

In case toString gets invoked at e. g. myPerson, the method will be looked up at this very instance' prototype chain. Because the method was found (immediately) at Person.prototype.toString, it automatically gets invoked within myPerson's context (something one also can achieve by explicitly invoking ... Person.prototype.toString.call(myPerson);).

class Person {
  constructor(name, address) {
    this.name = name;
    this.address = address;
  }
  toString() {
    return `name: ${ this.name }, address: ${ this.address }`
  }
}
const myPerson = new Person('John Doe', '123 Main St Anytown');

console.log(
  'Object.keys(myPerson) :',
  Object.keys(myPerson)
);
console.log('\n');

console.log(
  "myPerson.hasOwnProperty('toString') ?",
  myPerson.hasOwnProperty('toString')
);
console.log(
  "Person.prototype.hasOwnProperty('toString') ?",
  Person.prototype.hasOwnProperty('toString')
);
console.log('\n');

// automatic protoypal delegation, hence an inherited method.
console.log(
  'myPerson.toString() :',
  myPerson.toString()
);

// explicit protoypal delegation ... easy and expectable.
console.log(
  'Person.prototype.toString.call(myPerson) :',
  Person.prototype.toString.call(myPerson)
);
console.log('\n');

// explicit protoypal delegation ... with an *alien* object.
console.log(
`Person.prototype.toString.call({
  name: 'Jane Doe',
  address: '123 Main St Anytown',
}) :`,
Person.prototype.toString.call({
  name: 'Jane Doe',
  address: '123 Main St Anytown',
}));
.as-console-wrapper { min-height: 100%!important; top: 0; }

Regarding the factory implementation of Person provided by the OP, one has to comment on the code and also is in need of sanitizing it (, with the sanitizing part of cause being an opinion based one) ...

function Person(name, address) {
  const _name = name;
  const _address = address;
  const toString = () => `name: ${ this.name }, address: ${ this.address }`
  return {
    _name,
    _address,
    toString
  };
}
const myPerson = Person('John Doe', '123 Main St Anytown');

console.log('myPerson :', myPerson);
console.log('myPerson + "" :', myPerson + "");
.as-console-wrapper { min-height: 100%!important; top: 0; }

... Besides the toString method featuring two sources of reference failures ... on one hand the naming conflict of this.name vs this._name and this.address vs this._address and on the other hand choosing an arrow function which in this case only "knows" about the global context as the toString method's this context ... there is also no (technical) need of the additional function scope of the constants _name, _address and toString.

All these problems are solved if one does implement the factory as straightforward as ...

function Person(name, address) {
  return {
    name,
    address,
    toString: function () {
      return `name: ${ this.name }, address: ${ this.address }`;
    }
  };
}
const myPerson = Person('John Doe', '123 Main St Anytown');

console.log('myPerson :', myPerson);
console.log('myPerson + "" :', myPerson + "");


// There is no inheritance involved for
// any object created by the above factory.

console.log(
  'Object.keys(myPerson) :',
  Object.keys(myPerson)
);

console.log(
  "myPerson.hasOwnProperty('toString') ?",
  myPerson.hasOwnProperty('toString')
);

console.log(
  "(Object.getPrototypeOf(myPerson) === Object.prototype) ?",
  (Object.getPrototypeOf(myPerson) === Object.prototype)
);
.as-console-wrapper { min-height: 100%!important; top: 0; }

As one can see too, from the additional logging of the above sanitized factory example, there is no inheritance involved for any object created by the above factory (besides the most basic one of Object.prototype).


It's time now for the "sub classing" versus "augmentation / composition / mixin" part ...

... and again, let's start with the class based version of an Employee as provided by the OP.

Having sub classed Employee from Person via extends and having implemented the super call within the Employee's constructor, one does, with every invocation of the latter, create an instance which features three own properties - salary from directly having invoked the Employee constructor as well as name and address from the super call which one also could achieve by a delegation call like ... Person.call(this, name, address) ... in case Person was not a class constructor but an ordinary constructor function (which is not related to JavaScript class). In the same time this instance gets associated with a prototype chain that will be unveiled by the logging of the next example code ...

class Person {
  constructor(name, address) {
    this.name = name;
    this.address = address;
  }
  toString() {
    return `name: ${ this.name }, address: ${ this.address }`
  }
}

class Employee extends Person {
  constructor(name, address, salary) {
    super(name, address)
    this.salary = salary
  }

  getAnnualSalary() { return 12 * this.salary }
}

const myEmployee = new Employee('John Doe', '123 Main St Anytown', 6000);


console.log(
  '(myEmployee instanceof Employee) ?',
  (myEmployee instanceof Employee)
);
console.log(
  '(myEmployee instanceof Person) ?',
  (myEmployee instanceof Person)
);
console.log('\n');

console.log(
  '(Object.getPrototypeOf(myEmployee) instanceof Employee) ?',
  (Object.getPrototypeOf(myEmployee) instanceof Employee)
);
console.log(
  '(Object.getPrototypeOf(myEmployee) instanceof Person) ?',
  (Object.getPrototypeOf(myEmployee) instanceof Person)
);
console.log('\n');

console.log(
  'Object.keys(myEmployee) :',
  Object.keys(myEmployee)
);
console.log('\n');

console.log(
  "myEmployee.hasOwnProperty('getAnnualSalary') ?",
  myEmployee.hasOwnProperty('getAnnualSalary')
);
console.log(
  "Employee.prototype.hasOwnProperty('getAnnualSalary') ?",
  Employee.prototype.hasOwnProperty('getAnnualSalary')
);
console.log('\n');

console.log(
  "myEmployee.hasOwnProperty('toString') ?",
  myEmployee.hasOwnProperty('toString')
);
console.log(
  "Employee.prototype.hasOwnProperty('toString') ?",
  Employee.prototype.hasOwnProperty('toString')
);
console.log(
  "Person.prototype.hasOwnProperty('toString') ?",
  Person.prototype.hasOwnProperty('toString')
);
console.log('\n');

// automatic protoypal delegation,
// hence an inherited method via
// `Employee.prototype.getAnnualSalary`.
console.log(
  'myEmployee.getAnnualSalary() :',
  myEmployee.getAnnualSalary()
);

// automatic protoypal delegation,
// hence an inherited method via
// `Person.prototype.toString`.
console.log(
  'myEmployee.toString() :',
  myEmployee.toString()
);
.as-console-wrapper { min-height: 100%!important; top: 0; }

In comparison to the above class based approach the implementation of an Employee factory which augments an object (literal) by mixing in additional properties via Object.assign is downright slim ...

function Employee(name, address, salary) {
  const getAnnualSalary = () => 12 * salary;
  return Object.assign({ getAnnualSalary }, Person(name, address));
}

... But again, the OP's implementation is error prone. This time it is due to keeping salary within the factory's local function scope. Thus salary never becomes (turns into) a public property like it does with its classy counterpart. It remains immutable within a closure that will be created every time the Employee factory gets invoked.

An implementation of Employee which does not create closures and makes salary a public and mutable property too might look close to the following code ...

function Person(name, address) {
  return {
    name,
    address,
    toString: function () {
      return `name: ${ this.name }, address: ${ this.address }`;
    }
  };
}

function Employee(name, address, salary) {
  return Object.assign(Person(name, address), {
    salary,
    getAnnualSalary: function () {
      return (12 * this.salary);
    }
  });
}

const myEmployee = Employee('John Doe', '123 Main St Anytown', 6000);

console.log(
  'myEmployee :',
  myEmployee
);

console.log(
  'myEmployee.getAnnualSalary() :',
  myEmployee.getAnnualSalary()
);
console.log(
  'myEmployee.toString() :',
  myEmployee.toString()
);
.as-console-wrapper { min-height: 100%!important; top: 0; }

From the above logging it should be quite obvious that the so called Concatenative Inheritance produces data blobs. There is no separation in between publically carried state (data properties) and behavior (methods that operate/process such state/data). More importantly, if it comes to managing encapsulation and controlled access of encapsulated data this approach does lose its advantage of being lightweighted and easy to grasp on.

One might consider this approach for a somehow limited amount of references, each with a manageable amount of properties. In my opinion, this technique of code-reuse, within the context of a prototype based language, should also not feature the name inheritance for it actively prevents any delegation; and the latter is the very core of how JavaScript manages inheritance.

I would call this approach and its related system of thoughts what it actually is … “Factory based Composable Units Of Reuse“.

And just to be clear, I personally am a strong advocate of Composition over Inheritance … there are, again in my opinion, just much nicer approaches for composition/mixin based code-reuse than the one the OP was struggling with.

Peter Seliger
  • 11,747
  • 3
  • 28
  • 37
  • I do really thank you for this deep explanation, and looking forward to seeing the rest of the code you are currently working on – Fadi Oct 18 '20 at 22:06
  • @Fadi ... Done. And in addition I also did fix the error prone original implementation of the examples which do cover the so called *Concatenative Inheritance*. – Peter Seliger Oct 18 '20 at 22:13
  • 1
    "In my opinion, this technique of code-reuse, within the context of a prototype based language, should also not feature the name inheritance for it actively prevents any delegation" ... totally agree, seems this term is now popping up on multiple places, all citing the same sources ... – Jonas Wilms Dec 22 '20 at 16:10