Milo711
Milo711

Reputation: 275

Calling a method of a super-super class

I'm having trouble accessing a method in a hierarchy when each class contains a method with the same name.

class A { 
    constructor(private name: string) { }
    notify() { alert(this.name) }
}

class B extends A { 
    constructor() {
        super("AAA")
    }

    notify() {alert("B") }
}

class C extends B { 
    notify() { alert("C") }

    callA() {
        this.notify(); // this alerts "C"
        super.notify(); // this alerts "B"

        // How to call notify() of the class A so it alerts "AAA"? 
    }
}

new C().callA();

Upvotes: 13

Views: 7484

Answers (3)

Gilbert
Gilbert

Reputation: 3304

If you have:

class X { hey() {}}
class Y extends X { hey(){}}
class Z extends Y {hey(){}}

You can invoke X::hey from Z like this:

Z extends Y {
   sayHey() {
      X.prototype.hey.call(this)
  }

Upvotes: 0

Estus Flask
Estus Flask

Reputation: 222369

Grandparent method can be reached by climbing prototype chain up:

class C extends B { 
    notify() { alert("C") }

    callA() {
        this.notify(); // this alerts "C"
        const grandparentNotify = super.__proto__.notify;
        grandparentNotify.call(this); // this alerts "AAA"
    }
}

__proto__ is used for illustrative purposes, because the proper way to get object prototype is Object.getPrototypeOf. Notice that super.__proto__ chain for grantparent prototype may be different between implementations (e.g. TypeScript and native).

Grandparent method shouldn't be reached, because this indicates design problem; a grandchild shouldn't be aware of grandparent methods. The use of call in methods is another sign that class design went wrong.

If there's a need to use a method from another class (it doesn't really matter whether it is grandparent) in extended class, this should be done explicitly, via a mixin. Since C doesn't need all grandparent methods and needs to avoid naming collisions, a method should be assigned directly:

interface C {
    grandparentNotify(): void;
}
class C extends B { 
    notify() { alert("C") }

    callA() {
        this.notify(); // this alerts "C"
        this.grandparentNotify(); // this alerts "AAA"
    }
}
C.prototype.grandparentNotify = A.prototype.notify;

The interfaces are merged, and grandparentNotify is accepted as C method by typing system. This way looks raw, but it is idiomatic way to assign a method.

A bit more smoother way that provides some overhead but requires no interface merging is a getter:

class C extends B { 
    notify() { alert("C") }

    get grandparentNotify() {
        return A.prototype.notify;
    }

    callA() {
        this.notify(); // this alerts "C"
        this.grandparentNotify(); // this alerts "AAA"
    }
}

Upvotes: 7

Titian Cernicova-Dragomir
Titian Cernicova-Dragomir

Reputation: 249536

While I question the design that requires you to do this, you can easily acieve this by getting the original method of A.prototype and using call:

class C extends B { 
    notify() { alert("C") }

    callA() {
        A.prototype.notify.call(this);
    }
}

Upvotes: 16

Related Questions