0

Composition is a useful alternative to inheritance when one wants to cascade method calls from child to parent, see child extended class method calls its super version but that still only sees child data

However, for the child to present an interface that is compatible with parent type, one must implement a potentially large number of stub methods that all have the same form, namely they just relay the call to the former parent (which is now a component).

My question here, is it possible to write a catch-all method? A catch-all method would be called when none of the other methods are called. The catch-all would then just relay calls to the parent component. Thus the stub would only have to be written once. Variations could be used to sort multiple inheritance, etc.

Something like this:

  class A {
      constructor(){
        this.x = "super x!";
      }
      f(){
        console.log("I am a super f()!");
      }
      logx(){
        this.f();
        console.log(this.x);
      }
    }

    class B {
      constructor(){
        this.a = new A();
        this.x = "derived x.";
      }
      f(){
        console.log("I am a derived f()");
      }
      logx(){
        this.a.logx();
      }
    }

    let b = new B;
    b.logx();
  I am a super f()!
    super x!

Hypothetically would instead be something like this:

 class A {
      constructor(){
        this.x = "super x!";
      }
      f(){
        console.log("I am a super f()!");
      }
      logx(){
        this.f();
        console.log(this.x);
      }
    }

    class B {
      constructor(){
        this.a = new A();
        this.x = "derived x.";
      }
      f(){
        console.log("I am a derived f()");
      }
      catch_all(method_name, ...args){
        this.a.method_name(...args);
      }
    }

    let b = new B;
    b.logx();
  I am a super f()!
    super x!
  • Why not just `class B extends A` then? Or just keep the composition? There is nothing wrong with `b.a.method()` – Jonas Wilms Apr 15 '19 at 20:53
  • the issues with extends were covered in https://stackoverflow.com/questions/55677926 Composition allows the parent to keep state, but only extends inherits methods. To use composition and to expose the composed objects methods requires typing a lot of method stubs. –  Apr 15 '19 at 22:09

2 Answers2

0

This builds on isepa's suggestion to use Fred Truter's function for listing methods. That is a nice concept, but it doesn't quite get us there. It needs a test so as not to clobber existing methods. Also just copying the methods is problematic because the parent this.variables won't be there or will alias in the child - note this is composition so they weren't intended to be inherited. Instead of copying, this turns it into a call.

    //Fred Truter's function:
    Object.methods = function (klass) {
      const properties = Object.getOwnPropertyNames(klass.prototype)
      properties.push(...Object.getOwnPropertySymbols(klass.prototype))
      return properties.filter(name => {
        const descriptor = Object.getOwnPropertyDescriptor(klass.prototype, name)
        if (!descriptor) return false
        return 'function' == typeof descriptor.value && name != 'constructor'
      })
    }

   Object.compose = function (obj0, obj1, ...constructor_args) {
    obj0[obj1.name] = new obj1(...constructor_args);
    Object.methods(obj1).forEach(
      method => {
        if(!obj0[method]) obj0[method] = (...args) => obj0[obj1.name][method](...args);
      }
    );
  }


    // shows using the composition operator:
    class A {
      constructor(){
        this.x = "super x!";
      }
      f(){
        console.log("I am a super f()!");
        console.log(this.x);
      }
      logx(){
        console.log(this.x);
        this.f();
      }
    }

    class B {
      constructor(){
        Object.compose(this, A);
        this.x = "derived x.";
      }
      // f(){ console.log("I am a derived f()"); }
      logx(){
        console.log(this.x);
        this.f();
      }
    }

    let b = new B;
    b.logx();
    b.f();

And the outputs, as expected:

derived x.
I am a super f()!
super x!
I am a super f()!
super x!

Then upon removing the comment from the child version of f, we get, as expected:

derived x.
I am a derived f()
  • 1
    No this is actually not good. It does reflect the method *on every instance on its own*. So if you create 10.000 instances, 10.000 functions are created. for each method. That is super slow and has a lot of overhead, and can't be optimized that well. – Jonas Wilms Apr 15 '19 at 20:57
  • yes, imagine for example composing with Array. Which is why I was looking for a 'catch-all'. This does prevent having to manually write them out. –  Apr 15 '19 at 22:03
-1

Using Fred Truter's function to pull methods from a class Iterate through methods and properties of an ES6 class:

Object.methods = function (klass) {
    const properties = Object.getOwnPropertyNames(klass.prototype)
    properties.push(...Object.getOwnPropertySymbols(klass.prototype))
    return properties.filter(name => {
        const descriptor = Object.getOwnPropertyDescriptor(klass.prototype, name)
        if (!descriptor) return false
        return 'function' == typeof descriptor.value && name != 'constructor'
    })
}

class A {
    constructor() {
        this.x = "super x!";
    }
    f() {
        console.log("I am a super f()!");
    }
    logx() {
        console.log(this.x + "log");
    }
}

class B {
    constructor() {
        this.a = new A();
        this.x = "derived x. ";
        Object.methods(A).forEach(method => this[method] = this.a[method]);
    }
    logx() {
        console.log(this.x + "log")
    }
}

let b = new B;
b.f(); //"I am a super f()!"
b.logx(); //"derived x. log"
iosepa
  • 381
  • 1
  • 8
  • 1
    You should use `Object.methods(A.prototype)` to define them on `B.prototype` (when they don't already exist there) – Bergi Apr 15 '19 at 18:06
  • though not a solution because the inherited methods will not necessarily work without inheriting their data, I did use the methods functions in another proposed solution. –  Apr 16 '19 at 09:56