"abstract" in TypeScript

18. Mai 2016

In objektorientierten Programmiersprachen (kurz: OOP-Sprachen) wie C# oder Java hat das Schlüsselwort „abstract“ mehrere Aufgaben. Zum einen suggeriert es dem Entwickler, dass bestimmte Klassen oder Klassen-Member nicht vollständig ausprogrammiert sind, auf der anderen Seite stellt es eine verbindliche Implementierungsrichtlinie für ableitende Klassen dar. Mit Version 1.6, welche Ende 2015 offiziell veröffentlicht wurde, hat das abstract-Keyword auch in die Programmiersprache TypeScript Einzug gehalten. Im Rahmen dieses Flurfunkartikels soll dessen Funktionsweise und Nutzen etwas näher erläutert werden.

Abstrakte TypeScript-Klassen und Konstruktoren-Regeln

Abstrakte Klassen vereinen die Vorteile von konkret implementierten Klassen und reinen Schnittstellen-Klassen. Letztere stellen ausschließlich Signaturen (z.B. von Methoden) zur Verfügung und werden in den Programmiersprachen C# und Java sowie in TypeScript einher mit dem Schlüsselwort interface versehen.  Des Weiteren können Variablen abstrakter Klassen zwar deklariert aber niemals direkt instanziiert werden. Nur durch eine konkrete Spezialisierung innerhalb der Vererbungshierarchie können Instanzen abstrakter Klassen erzeugt werden.

TypeScript orientiert sich bezüglich dieses Verhaltens an den etablierten Programmiersprachen wie C# oder Java.  Die Syntax für eine einfache abstrakte GeometricFigure-Klasse schaut in TypeScript wie folgt aus:

   1: abstract class GeometricFigure {

   2: }

Des Weiteren können abstrakte Klassen mit parametrisierten Konstruktoren ausgestattet werden. Das folgende Code-Snippet zeigt dies exemplarisch:

   1: abstract class GeometricFigure {

   2:     public name: string;

   3:     constructor(name: string) {

   4:         this.name = name;

   5:     }

   6: }

Wie bereits erwähnt, können in C# und Java keine direkten Instanzen von abstrakten Klassen angelegt werden. Auch in TypeScript ist das nicht möglich. Folgender Instanziierungsversuch schlägt daher mit einer entsprechenden Meldung durch den Compiler fehl:

Grafik1a_M.Pohl

Es bedarf also einer konkreten bzw. nicht abstrakten Klasse, welche von der abstrakten Basisklasse ableitet, um Instanzen anzulegen. Das folgende Code-Beispiel repräsentiert eine solche real implementierte konkrete Klasse:

   1: class Circle extends GeometricFigure {

   2:     public radius: number;

   3: }

Nun kann auch eine GeometricFigure-Instanz ohne Kompilierungsfehler angelegt werden.

   1: var geometricFigure: GeometricFigure = new Circle("Circle");

 

Interessant an diesem Beispiel ist die Tatsache, dass ein bestehender Konstruktor in der Basisklasse nicht in der ableitenden Klasse mit ausprogrammiert werden muss. Hierin unterscheidet sich TypeScript von OO-Sprachen wie C# oder Java. Der TypeScript-Compiler erkennt automatisch die Vererbungshierarchie und erzeugt anschließend die konkreten  Objekt-Instanzen.

Wenn der Konstruktor der Basisklasse aufgerufen werden soll, so muss dies mittels des Schlüsselworts super und als erste Code-Anweisung innerhalb des Konstruktor-Codes erfolgen. Fehlt dieser Aufruf, auch wenn die Basisklasse keinen Konstruktor besitzt, kommt wieder eine Compiler-Meldung mit entsprechendem Hinweis:

Grafik2_M.Pohl

Diese Regel dürfte für C#- oder Java-Entwickler ebenfalls vertraut vorkommen. Besitzt beispielswiese eine ableitende C#-Klasse einen weiteren Konstruktor, dessen Signatur sich vom Konstruktor der abstrakten Basisklasse unterscheidet, so muss letzterer mit Hilfe des base-Schlüsselworts aufgerufen werden. Eine korrekt implementierte konkrete Circle-Klasse, mit einem anschließender Instanz-Erzeugung stellt das folgende Code-Snippet dar:

   1: class Circle extends GeometricFigure

   2: {

   3:     radius: number;

   4:

   5:     constructor(name: string, radius: number)

   6:     {

   7:         super(name);

   8:         this.radius = Math.max(radius, 0);

   9:     }

  10: }

  11:

  12: var geometricFigure: GeometricFigure = new Circle("Circle", 73);

 

Abstrakte Methoden

Nicht nur Klassen können in modernen objekt-orientierten Programmiersprachen (kurz: OOP-Sprachen) als abstrakt deklariert werden, auch Methoden können mit dem abstract-Keywort ausgestattet werden. Diese stellen mit ihrer Signatur und Rückgabewert eine Art bindenden Vertrag für ableitende Klassen dar und müssen von diesen dann konkret implementiert werden. Genau nach diesen Vorgaben verhält es sich auch in TypeScript.

Abstrakte Methoden können nur in abstrakten Klassen deklariert werden. Der Versuch, eine abstrakte Methode in einer konkreten Klasse anzulegen, erzeugt, wie in folgender Abbildung exemplarisch dargelegt, eine Compiler-Meldung:

Grafik-M.Pohl3a

Ebenso verhält es sich, wenn in der konkreten Klasse die Implementierung einer abstrakten Methode aus der Basisklasse fehlt:

Grafik M.Pohl4

Eine korrekte Implementierung des oben aufgeführten Beispiels einer abstrakten Basisklasse mit einer abstrakten Methode, stellen die folgenden beiden Code-Ausschnitte dar:

   1: abstract class GeometricFigure {

   2:     public name: string;

   3:

   4:     constructor(name: string) {

   5:         this.name = name;

   6:     }

   7:

   8:     abstract calculateArea(): number;

   9: }

  10:

  11: class Circle extends GeometricFigure

  12: {

  13:     public radius: number;

  14:

  15:     constructor(name: string, radius: number)

  16:     {

  17:         super(name);

  18:         this.radius = Math.max(radius, 0);

  19:     }

  20:

  21:     calculateArea(): number

  22:     {

  23:         return Math.pow(this.radius, 2) * Math.PI;

  24:     }

  25: }

Die anschließende Instanziierung und der anschließende Funktionsaufruf sehen wie folgt aus:

   1: var geometricFigure: GeometricFigure = new Circle("Circle", 73);

   2: var calculatedArea = geometricFigure.calculateArea();

Abstrakte Properties

Die Programmiersprache Java kennt (zum aktuellen Zeitpunkt) keine Propertys. Sämtliche Aktionen erfolgen entweder durch direkten Zugriff auf die einzelnen Felder, sofern mit dem richtigen Modifier versehen, oder über Methoden-Aufrufe. C# kennt Propertys, welche Eigenschaften von Klassen repräsentieren und auch als abstrakt deklariert werden können. TypeScript-Klassen können sich zwar des im .NET-Framework bekannten Property-Konzeptes bedienen, allerdings ist es nicht möglich, diese mit dem abstract-Schlüsselwort zu versehen. Hier gibt der Compiler wieder unmittelbar eine entsprechend Fehlermeldung zurück.

Grafik M.Pohl8 Kopie

Die Verwendung des abstract-Schlüsselwortes beschränkt sich momentan also ausschließlich auf Klassen und Methoden.

Fazit

Wie jede Programmiersprache, hat auch TypeScript einige gewöhnungsbedürftige Eigenschaften.  Die in diesem Artikel aufgezeigte Regel für parameterlose Konstruktoren in konkreten Klassen bzgl. Vererbungshierarchien zählt sicherlich hierzu.

Der Artikel belegt aber auch, dass das eingeführte abstract-Schlüsselwort eine nützlich Erweiterung der noch recht jungen Programmiersprache TypeScript (Erscheinungsjahr 2012) darstellt. Es ermöglicht, bei der Entwicklung von Web-Applikationen, objektorientierte Paradigmen für Vererbungshierarchien zu berücksichtigen, welche aus anderen OOP-Sprachen heute kaum noch wegzudenken sind. Zwar kann das abstract-Schlüsselwort zum aktuellen Zeitpunkt nur auf TypeScript-Klassen und deren Methoden angewendet werden, dennoch dürfte sich seine Verwendung in Zukunft immer größer werdender Beliebtheit erfreuen.

Quellen

  1. Announcing TypeScript 1.6 (Letzter Zugriff: 2016-04-25)
  2. What’s new in TypeScript (Letzter Zugriff: 2016-04-25)
  3. TypeScript (Letzter Zugriff: 2016-04-25)
  4. Wikipedia: TypeScript (Letzter Zugriff: 2016-04-25)