1

Background: I am working on an Angular 1.x project that integrates itself with some AWS services. I have a service for each of the DynamoDB tables that I use that manages basic operations like put, query, get, update, etc.. These services are then imported in a "main" service that adds some more complex functions (like read from a table and insert into another). In the table classes I have both "private" functions (I recognize them because they all start with "_") and public. The general service uses both. Every service has the ES6 class syntax, so my table class will be like the following:

export default class {
    constructor(some, var, and, const) {
        this.some = some;
        this.var = var;
        this.and = and;
        this.const = const;
        ...
    }

    _myPrivateMethod() {
        console.log(`I'm private!`);
    }

    myPublicMethod() {
        console.log(`I'm public!`);
    }
}

Problem: I want to be able to export only the main service into my Angular controllers, and not all of the "table" service, so my goal is to implement a multiple class inheritance, where my main service inherits from all the table services and exposes all of their methods (public and private, I don't care), so that into my controller I import just my main service and I have everything I need.

Some solutions I've found: I read some similar questions here, but none of the answers could fit my case, since I need to pass different vars into my table services' constructors. I'd like to not use any external library that makes easier multiple inheritance, because the project is going to be big and I don't want to keep adding stuff, so I'd like to implement it myself.
The best idea that I've got consist in creating the instances of the child classes, getting their prototypes with Object.getPrototypeOf, and setting the prototype of this with Object.setPrototypeOf with all them via Object.assign, like this:

import TableClass1 from './tables/1';
import TableClass2 from './tables/2';

export default class MyMainClass {
    constructor(some, var, other, param) {
        let myTableInstance1 = new TableClass1(some, var);
        let myTableInstance2 = new TableClass2(other, param);
        let tmpMultiInherit = Object.assign({}, Object.getPrototypeOf(myTableInstance1), Object.getPrototypeOf(myTableInstance2));

        Object.setPrototypeOf(this, tmpMultiInherit);
    }
}

This seems a good idea to me, because the getPrototypeOf(s) functions returns me exactly what I need to add to my class in the typeof object, but the Object.assign line returns {}, and I cannot understand why.

This question was the most useful I've found, but none of the answers explain how can you implement multi inheritance with different parent classes' constructors' forms.

So if any of you knows why this method fails or has had this problem in the past, I'd be most grateful for your help.

Thank you in advance!

1 Answers1

1

Why do you need multiple inheritance in the first place? Almost all problems are better solved without inheritance, and definitely without the headaches that come with multiple inheritance.

In this case, why not create a service that is composed of these table classes, rather than inheriting from all of them (composition over inheritance)?

import TableClass1 from './tables/1';
import TableClass2 from './tables/2';

export default class MyMainClass {
    constructor(some, var, other, param) {
        this.table1 = new TableClass1(some, var);
        this.table2 = new TableClass2(other, param);
    }
}

It's easier to write, easier to debug, and easier to test. Especially if, instead of creating new TableClass1 instances in the constructor, you pass them in via dependency injection.


As to why your "multiple inheritance" solution doesn't work: the Object.assign call only copies fields from the prototypes, but not fields set on the objects themselves. Nor does it go up the prototype chain, so it doesn't copy anything from parent classes either. A plain for (prop in obj) loop should help to catch everything.

Still you're going to have to be very careful, because any captured this pointers will still point to the original myTableInstance1 and myTableInstance2 objects and end up doing the wrong thing in very nonobvious ways. Overall, this smells like a source of subtle, hard to find bugs, and I would strongly advise against this approach.

Thomas
  • 174,939
  • 50
  • 355
  • 478
  • Thank you Thomas for your reply. I know multiple inheritance is generally bad, but your solution would imply that if I need to access from my controller to a method in TableClass1, I'd have to write `MyMainService.table1.method()`. I'd prefer instead to write just `MyMainService.method()` (knowing of course that the name `method` is unique in all classes inherited in my main class). On the other hand I don't want to create a method in MyMainClass that just calls the same method in TableClass1, because they could grow by number. – Mattia Costamagna Jan 08 '18 at 10:13
  • 1
    I would just stick with `MyMainService.table1.method()`, or better, inject the `TableClass1` instance directly. The latter adheres more to the "consume only what you use" guideline, and will also improve testability because you don't need to mock out the entire `MyMainService`. – Thomas Jan 08 '18 at 10:18
  • Before changing my code to what you've suggested I've tried to change my `tmpMultiInherit` value to `Object.assign({}, myTableInstance1, myTableInstance2)` like you said in your answer, but it just copies the properties, and not the methods, which are what I need. Isn't that strange? My instances have their methods in the prototype, so `Object.assign` should copy them into tmpMultiInherit, right? I've created a JSFiddle here: https://jsfiddle.net/6w5vjg7b/. As you can see `multiInherit.method1();` doesn't work and throws the error "TypeError: multiInherit.method1 is not a function" – Mattia Costamagna Jan 08 '18 at 13:46
  • Sorry, I forgot that `Object.assign` only copies the object's own properties, and doesn't traverse up the prototype chain. A regular `for (prop in obj) { ... }` loop should work better. – Thomas Jan 08 '18 at 14:08
  • Mmmmm same result with the for loop, but it works if I define my methods inside the constructor with `this.method = function() { ... }`. I've updated the code: https://jsfiddle.net/6w5vjg7b/2/. The only method that gets printed out is `testMethod`, which is the only one created inside the constructor, and therefore it is copied inside multiInherit. – Mattia Costamagna Jan 08 '18 at 14:16
  • I made it work by changing the syntax of the subclasses from ES6 to the function style. Now everything gets copied inside the main class, but I still can't figure out why with the ES6 class syntax it does not work. Here is the updated fiddle: https://jsfiddle.net/6w5vjg7b/3/. Anyhow I'll set your answer as accepted because is valid, and I've found a workaround (even though I'll probably change my code to what you suggested), but if anyone knows a better way, or why the ES6 syntax throws the error, I'm still interested! – Mattia Costamagna Jan 08 '18 at 14:40
  • It works because you're assigning to `this.method1` in the constructor. This causes `method1` to become a property of the object, rather than the prototype. More customary style (and more memory-efficient) would be to set `MyClassFunction1.prototype.method1 = function() { ... };` outside the constructor, which is what the ES6 `class` syntax essentially does. If you don't have a good grasp of these essentials, I would even more strongly suggest to stay away from emulating multiple inheritance :) – Thomas Jan 08 '18 at 14:52