kackb00n: "Vererbung"

Hallo,

ich hab mir jetzt schon einiges zum Prototyping in Javascript durchgelesen, komme aber irgendwie nicht weiter.

Als erstes will ich hier mal in Java zeigen, was ich in Javascript umsetzen will:

public class A {
	private int a = 0;
	public int doSomething() {
		a++;
		return a;
	}
}

public class B extends A {
	private int b = 0;
	public int get() { return b; }
	public void set(val) { b = val; }
}

public class C extends B {
	@Override
	public void set(val) {
		if(val > 10) throw new Exception("error");
		
		super.set(val);
	}
}

Einer meiner Versuche es in Javascript zu implementieren:

function A() {
	var a = 0;
	this.doSomething = function() {
		a++;
		return a;
	}
}

function B() {
	A.call(this);
	
	var b = 0;
	this.get = function() { return b; };
	this.set = function(val) {
		b = val;
	}
}
B.prototype = Object.create(A.prototype);

function C() {
	B.call(this);
	
	this.set = function(val) {
		if(val > 10) throw new Error("error");
		
		C.prototype.set(val); // Uncaught TypeError: C.prototype.set is not a function
	}
}
C.prototype = Object.create(B.prototype);

Wenn ich es richtig verstehe wird bei C.prototype.set(val); der Fehler geworfen, weil ich in B die Funktion set direkt an die erzeugte Instanz hänge und nicht an deren Prototypen. Dann dachte ich mir, dass ich den Prototypen von C anders erstellen muss also habe ich C.prototype = Object.create(new B()); probiert. Da kamen dann aber nach get-Aufrufen falsche Werte. Meine Vermutung ist, dass dies dadurch geschieht, dass alle C-Instanzen nur auf die eine im Closure hängenden Variable b (siehe function B()) zugreifen.

Je mehr ich jetzt darüber lese um so verwirrter bin ich, deswegen wende ich mich jetzt an euch: Wie mach ich es richtig? Kann mir jemand für Obiges den Code in Javascript zeigen?

Danke im Voraus!

MfG kackb00n

akzeptierte Antworten

  1. Kaum hab ich mein Problem beschrieben und bin eine rauchen gegangen ist mir eine Lösung eingefallen: Anstatt in C einfach nur this.set = function() {...} auszuführen speicher ich vorher this.set in einer lokalen Variable, also wie folgt:

    function C() {
    	B.call(this);
    	
    	var baseSet = this.set;
    	this.set = function(val) {
    		if(val > 10) throw new Error("error");
    		
    		baseSet.call(this, val);
    	}
    }
    C.prototype = Object.create(B.prototype);
    

    Diese Lösung funktioniert zwar, aber es wirkt irgendwie nicht richtig für mich.

    1. Kaum hab ich mein Problem beschrieben und bin eine rauchen gegangen ist mir eine Lösung eingefallen

      Da hätte ich besser mit Dir eine geraucht, statt eine halbe Stunde lang für dich zu schreiben. Aber ich bin leider Nichtraucher...

      Aber guck Dir das Module-Pattern trotzdem mal an, das ist eine gute Möglichkeit zum Kapseln von Komponenten.

      Rolf

  2. Der wesentliche Unterschied zwischen Java und Javascript ist hier, dass Methoden in Java grundsätzlich an der Klasse hängen. Ich weiß nicht GENAU, wie Java das unter der Haube deichselt, aber es sollte für jede Klasse eine VFT (virtual function table) geben, worin sich pro Methode ein Zeiger auf die Implementierung findet, und die VFT einer abgeleiteten Klasse ist die Kopie der VFT ihrer Superklasse, verlängert um die eigenen Methoden (weshalb mehrfache Vererbung in Java verboten ist; C++ bricht sich einige Register aus dem Prozessor um die VFTs bei mehrfacher Vererbung im Griff zu halten). Ein super-Aufruf sorgt einfach dafür, dass nicht die VFT der eigenen Klasse, sondern die VFT der Superklasse verwendet wird.

    Um das zu übertragen, könnte man formulieren: In JavaScript hat jedes Objekt seine eigene VFT, nämlich die Function-Properties des Objekts, und erbt weitere VFTs über die Prototypenkette.

    Dadurch, dass Du B.get und B.set im Konstruktor ans Objekt hängst (um das private b zu kapseln), und dann in C() eine Zuweisung this.set = ... machst, überschreibst Du den VFT-Bereich, der eigentlich der Superklasse gehört. Daraus resultieren Deine Schwierigkeiten.

    Lösungsmöglichkeiten:

    1. Du verlegst B.get und B.set an den Prototypen von B.

      Nachteil: Du musst Dir etwas anderes einfallen lassen, um var b private zu bekommen. Hier kann Dir das Module-Pattern helfen und ein Symbol-Objekt, um aus b ein echtes Property zu machen, das aber nur mit Kenntnis des Symbols erreichbar ist (na gut, ohne Kenntnis des Symbols auch, aber das braucht dann schon die Brechstange). Und weil das Symbol Teil des IIFE-Scopes ist, kommt nur der Code heran, der innerhalb der IIFE steht.

      Damit hast Du die Möglichkeit, in C einen Aufruf wie B.prototype.set.call(this, val) zu machen. Kleiner Happen am Rande ist, dass ich die Superklasse als Parameter in die IIFE hineingebe, die den Konstruktor von B einschließt, und so innerhalb der IIFE keine Referenz auf einen externen Scope brauche.

      Vorteil des Module-Patterns ist, dass Du die Funktionsobjekte für get und set nur einmal bildest und nicht pro Objektkonstruktion. Und du hängst nicht den Scope-Kontext des Konstruktoraufrufs an jede Instanz dieser Funktionen. Falls Du viele B oder C Objekte erzeugst, ist das durchaus Laufzeit- und Speicherrelevant.

    B = (function(baseClass) {
       let bKey = new Symbol();
       let BFactory = function() {
          baseClass.call(this);
          this[bKey] = 0;
       }
       BFactory.prototype = Object.create(baseClass.prototype);
       BFactory.prototype.get = function() { return this[bKey]; };
       BFactory.prototype.set = function(b) { this[bKey] = b; };
    
       return BFactory;
    })(A);
    
    1. (das hier ist Nummer 2, das Forum macht eine 1 draus...) Ich vermute, dass folgende Konstruktion von C ebenfalls funktionieren sollte. Dann kannst Du B unverändert lassen.
    function C() {
       B.call(this);
       let super_set = this.set;
       this.set = function(val) {
          if(val > 10) throw new Error("error");
          super_set(val);
       }
    }
    C.prototype = Object.create(B.prototype);
    

    Aber wie gesagt - wenn Du viele Objekte erzeugst, ist das Module-Pattern ökonomischer.

    Rolf

  3. Tach!

    Kann mir jemand für Obiges den Code in Javascript zeigen?

    Ich kann dir zumindest TypeScript empfehlen. Das fühlt sich so ähnlich an wie C# und übersetzt nach Javascript-Code. Auf der Website gibt es auch den Playground, der dir die Übersetzung direkt anzeigen kann. Neben Freitext kann man da auch ein paar Beispiel wählen, unter anderem Classes und Inheritance.

    dedlfix.

    1. Typescript wird auch bei uns in der Firma angepriesen, aber ich frage mich ob man sich damit in eine Lock-In Situation begibt. Es ist ein Microsoft-Produkt, auch wenn's OpenSource ist.

      Rolf

      1. Tach!

        Du hast immer noch das erzeugte Javascript, falls TypeScript zu existieren aufhören sollte. Außerdem ist es Open Source. Und man wird dir sicherlich nicht deine Kopie der benötigten Tools fernzünden.

        dedlfix.