Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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();
like image 213
Milo711 Avatar asked Mar 01 '18 18:03

Milo711


2 Answers

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);
    }
}
like image 115
Titian Cernicova-Dragomir Avatar answered Sep 20 '22 15:09

Titian Cernicova-Dragomir


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"
    }
}
like image 41
Estus Flask Avatar answered Sep 18 '22 15:09

Estus Flask