Читать книгу Angular - Manfred Steyer - Страница 38

Interfaces

Оглавление

Um den Einsatz von Interfaces zur Schaffung austauschbarer Objekte zu veranschaulichen, greifen wir in Beispiel 2-11 das bereits verwendete Flight-Interface wieder auf:

Beispiel 2-11: Interface mit Methode

// src/app/flight.ts

export interface Flight {

id: number;

from: string;

to: string;

date: string;

distance?: number;

calcPrice?(): number;

}

Als Ergänzung bekommt Flight zwei weitere Einträge spendiert: distance gibt Auskunft über die Länge der Flugstrecke, und calcPrice ist eine Methode zum Berechnen des Flugpreises. Der Fragezeichenoperator (?) gibt an, dass diese beiden Erweiterungen optional sind.

Im Gegensatz zu Klassen definiert ein Interface für Methoden nur eine Signatur, jedoch keinen Körper mit einer gewünschten Logik. Das Bereitstellen des Körpers ist Aufgabe jener Klassen, die das Interface implementieren. Ein weiterer Unterschied zu Klassen ist, dass sämtliche Einträge eines Interface per definitionem öffentlich (public) sind.

Die Klasse ScheduledFlight in Beispiel 2-12 implementiert Flight explizit. Dazu listet sie dieses Interface in ihrer implements-Klausel auf. Das veranlasst den Compiler, zu prüfen, ob sich ScheduledFlight an die Vorgaben des Interface hält.

Beispiel 2-12: Implementierung von Flight

// src/app/ts/scheduled-flight.ts

import { Flight } from '../flight';

export class ScheduledFlight implements Flight {

id: number = 0;

from: string = '';

to: string = '';

date: string = '';

distance: number = 0;

calcPrice(): number {

return this.distance / 3;

}

}

Bitte beachten Sie hier, dass jede Eigenschaft bewusst einen Standardwert zugewiesen bekommt. Das hängt damit zusammen, dass die Angular CLI seit Version 12 standardmäßig den sogenannten Strict Mode verwendet. Unter anderem verpflichtet dieser TypeScript dazu, strengere Prüfungen durchzuführen. Außerdem verbietet dieser Modus die standardmäßige Verwendung von nicht initialisierten Variablen. Wir gehen weiter unten unter »Strikte Null-Prüfungen« auf Seite 66 darauf etwas näher ein.

Eine Klasse kann beliebig viele Interfaces implementieren. Diese sind kommagetrennt in der implements-Klausel anzugeben: class ScheduledFlight implements Flight, Billable, Discountable, Offer { [...] } In diesem Fall muss die Klasse sämtliche Vorgaben aller Interfaces erfüllen.

Die Klasse CharterFlight aus Beispiel 2-13 implementiert ebenfalls das Interface Flight. Allerdings unterscheidet sich die Implementierung von calcPrice im Detail von jener in ScheduledFlight.

Beispiel 2-13: Eine weitere Implementierung des Interface Flight

// src/app/ts/charter-flight.ts

import { Flight } from '../flight';

export class CharterFlight implements Flight {

id: number = 0;

from: string = '';

to: string = '';

date: string = '';

distance: number = 0;

calcPrice(): number {

return this.distance / 2;

}

}

Dadurch, dass beide Klassen das Interface implementieren, können beide Variablen des Interface-Typs Flight zugewiesen werden. Somit kann ein ScheduledFlight-Objekt ein CharterFlight-Objekt ersetzen, wie Sie in Beispiel 2-14 sehen:

Beispiel 2-14: Austauschen von Objekten

// src/app/ts/demo.ts

import { Flight } from '../flight';

import { ScheduledFlight } from './scheduled-flight';

import { CharterFlight } from './charter-flight';

[...]

let oneMoreFlight: Flight = new ScheduledFlight();

oneMoreFlight.distance = 1000;

if (oneMoreFlight.calcPrice) {

console.debug('Preis', oneMoreFlight.calcPrice());

}

oneMoreFlight = new CharterFlight();

// Ersetzen; dieselbe Variable zeigt nun

// auf einen CharterFlight.

oneMoreFlight.distance = 1000;

if (oneMoreFlight.calcPrice) {

console.debug('Preis', oneMoreFlight.calcPrice()); // neuer Preis

}

Da calcPrice im Interface als optionale Methode definiert wurde, ist das if, das deren Vorhandensein prüft verpflichtend.

Sie können aber auch Code schreiben, der verschiedene Ausprägungen des Flight-Interface auf die gleiche Weise verwendet. Dies nennt man auch Polymorphie. Ein Beispiel dafür bietet Beispiel 2-15.

Beispiel 2-15: Polymorphe Behandlung zweier Ausprägungen von Flight

// src/app/ts/demo.ts

import { Flight } from '../flight';

import { ScheduledFlight } from './scheduled-flight';

import { CharterFlight } from './charter-flight';

[...]

const scheduledFlight: Flight = new ScheduledFlight();

scheduledFlight.distance = 1000;

const charterFlight: Flight = new CharterFlight();

charterFlight.distance = 1000;

const myFlights: Flight[] = [scheduledFlight, charterFlight];

for(const f of myFlights) {

if (f.calcPrice) {

console.debug('Preis', f.calcPrice());

}

}

Das Array flights vom Typ Flight[] beinhaltet sowohl einen ScheduledFlight als auch einen CharterFlight. Die Schleife geht auch nicht auf die Unterschiede zwischen diesen beiden Ausprägungen ein, sondern nutzt die vom Interface vorgegebenen Funktionen zur Preisberechnung. Da calcPrice, wie oben erwähnt, eine optionale Methode ist, prüft die Schleife mit einem if, ob diese Methode existiert.

Interfaces verschwinden beim Kompilieren und existieren somit nicht mehr zur Laufzeit. Der Grund dafür ist, dass dynamische Sprachen wie JavaScript gänzlich ohne dieses Konstrukt auskommen.

Das hier gezeigte Beispiel funktioniert auch, wenn die Klassen das Interface nicht in ihrer implements-Klausel erwähnen. In diesem Fall prüft der Compiler jedoch bei der Klassendeklaration nicht, ob die Klasse das Interface implementiert. Allerdings findet eine ähnliche Prüfung bei der Zuweisung zur Variablen des Interface-Typs statt:

let scheduledFlight: Flight = new ScheduledFlight();

An dieser Stelle prüft der Compiler, ob der ScheduledFlight eine zum Flight kompatible Struktur aufweist. Ist dem nicht so, mahnt er die Zuweisung mit einem Fehler an. Man spricht hierbei auch von Duck-Typing: Was aussieht wie eine Ente, ist eine Ente.

Angular

Подняться наверх