Zum Inhalt springen
TypeScript Fortgeschritten 30 min

Vererbung in TypeScript

Lerne Vererbung mit extends, super und Method Overriding in TypeScript-Klassen.

Aktualisiert:

Vererbung in TypeScript

Vererbung ermöglicht es Klassen, Eigenschaften und Methoden von anderen Klassen zu erben. TypeScript erweitert JavaScript-Vererbung um bessere Typisierung.

Grundlagen der Vererbung

// Basisklasse
class Animal {
    constructor(public name: string) {}

    speak(): string {
        return `${this.name} makes a sound`;
    }

    move(distance: number): string {
        return `${this.name} moved ${distance} meters`;
    }
}

// Abgeleitete Klasse
class Dog extends Animal {
    constructor(name: string, public breed: string) {
        super(name);  // Konstruktor der Basisklasse aufrufen
    }

    // Methode überschreiben
    speak(): string {
        return `${this.name} barks: Woof!`;
    }

    // Neue Methode
    fetch(): string {
        return `${this.name} fetches the ball`;
    }
}

const dog = new Dog("Buddy", "Golden Retriever");
console.log(dog.speak());     // "Buddy barks: Woof!"
console.log(dog.move(10));    // "Buddy moved 10 meters" (geerbt)
console.log(dog.fetch());     // "Buddy fetches the ball"

super Keyword

super im Konstruktor

class Person {
    constructor(
        public firstName: string,
        public lastName: string
    ) {}

    get fullName(): string {
        return `${this.firstName} ${this.lastName}`;
    }
}

class Employee extends Person {
    constructor(
        firstName: string,
        lastName: string,
        public employeeId: string,
        public department: string
    ) {
        // super() muss vor this aufgerufen werden!
        super(firstName, lastName);
    }

    getInfo(): string {
        return `${this.fullName} (${this.employeeId}) - ${this.department}`;
    }
}

const employee = new Employee("Max", "Mustermann", "E001", "Engineering");
console.log(employee.getInfo());  // "Max Mustermann (E001) - Engineering"

super für Methoden

class Vehicle {
    constructor(public brand: string) {}

    start(): string {
        return `${this.brand} is starting...`;
    }

    stop(): string {
        return `${this.brand} has stopped`;
    }
}

class Car extends Vehicle {
    constructor(
        brand: string,
        public model: string
    ) {
        super(brand);
    }

    // Erweitert die Eltern-Methode
    start(): string {
        const parentMessage = super.start();  // Eltern-Methode aufrufen
        return `${parentMessage} Engine running!`;
    }
}

const car = new Car("BMW", "M3");
console.log(car.start());  // "BMW is starting... Engine running!"

Method Overriding

Grundlegendes Überschreiben

class Shape {
    constructor(public color: string) {}

    getArea(): number {
        return 0;
    }

    describe(): string {
        return `A ${this.color} shape`;
    }
}

class Rectangle extends Shape {
    constructor(
        color: string,
        public width: number,
        public height: number
    ) {
        super(color);
    }

    // Override
    getArea(): number {
        return this.width * this.height;
    }

    // Override mit super
    describe(): string {
        return `${super.describe()} - Rectangle ${this.width}x${this.height}`;
    }
}

class Circle extends Shape {
    constructor(
        color: string,
        public radius: number
    ) {
        super(color);
    }

    getArea(): number {
        return Math.PI * this.radius ** 2;
    }

    describe(): string {
        return `${super.describe()} - Circle with radius ${this.radius}`;
    }
}

const rect = new Rectangle("blue", 10, 5);
const circle = new Circle("red", 7);

console.log(rect.getArea());     // 50
console.log(circle.getArea());   // 153.94...
console.log(rect.describe());    // "A blue shape - Rectangle 10x5"

override Keyword (TypeScript 4.3+)

class Animal {
    speak(): void {
        console.log("Some sound");
    }
}

class Dog extends Animal {
    override speak(): void {  // Explizit markieren
        console.log("Woof!");
    }

    // Fehler mit noImplicitOverride: true
    // override run(): void {}  // Error: This member cannot have an 'override' modifier
}

Aktiviere in tsconfig.json:

{
    "compilerOptions": {
        "noImplicitOverride": true
    }
}

Polymorphismus

class Animal {
    constructor(public name: string) {}

    speak(): string {
        return `${this.name} makes a sound`;
    }
}

class Dog extends Animal {
    speak(): string {
        return `${this.name}: Woof!`;
    }
}

class Cat extends Animal {
    speak(): string {
        return `${this.name}: Meow!`;
    }
}

class Bird extends Animal {
    speak(): string {
        return `${this.name}: Tweet!`;
    }
}

// Polymorphismus: Verschiedene Typen, gleiche Schnittstelle
const animals: Animal[] = [
    new Dog("Buddy"),
    new Cat("Whiskers"),
    new Bird("Tweety")
];

animals.forEach(animal => {
    console.log(animal.speak());
});
// "Buddy: Woof!"
// "Whiskers: Meow!"
// "Tweety: Tweet!"

instanceof und Type Guards

class Vehicle {
    drive(): void {
        console.log("Driving...");
    }
}

class Car extends Vehicle {
    honk(): void {
        console.log("Beep beep!");
    }
}

class Motorcycle extends Vehicle {
    wheelie(): void {
        console.log("Doing a wheelie!");
    }
}

function handleVehicle(vehicle: Vehicle): void {
    vehicle.drive();

    if (vehicle instanceof Car) {
        vehicle.honk();  // TypeScript weiß: vehicle ist Car
    } else if (vehicle instanceof Motorcycle) {
        vehicle.wheelie();  // TypeScript weiß: vehicle ist Motorcycle
    }
}

handleVehicle(new Car());        // "Driving..." "Beep beep!"
handleVehicle(new Motorcycle()); // "Driving..." "Doing a wheelie!"

Mehrfach-Vererbung (nicht direkt möglich)

TypeScript unterstützt keine Mehrfach-Vererbung, aber Mixins:

// Mixin Pattern
type Constructor<T = {}> = new (...args: any[]) => T;

function Timestamped<TBase extends Constructor>(Base: TBase) {
    return class extends Base {
        createdAt = new Date();
        updatedAt = new Date();

        touch() {
            this.updatedAt = new Date();
        }
    };
}

function Activatable<TBase extends Constructor>(Base: TBase) {
    return class extends Base {
        isActive = true;

        activate() {
            this.isActive = true;
        }

        deactivate() {
            this.isActive = false;
        }
    };
}

// Basis-Klasse
class User {
    constructor(public name: string) {}
}

// Mixins anwenden
const TimestampedUser = Timestamped(User);
const FullUser = Activatable(TimestampedUser);

const user = new FullUser("Max");
console.log(user.name);        // "Max"
console.log(user.createdAt);   // Date
console.log(user.isActive);    // true

user.deactivate();
user.touch();

Praktische Beispiele

UI Component Hierarchy

abstract class Component {
    protected container: HTMLElement | null = null;

    constructor(protected selector: string) {}

    abstract render(): string;

    mount(): void {
        this.container = document.querySelector(this.selector);
        if (this.container) {
            // Sicheres Rendering mit textContent für einfachen Text
            this.container.textContent = this.render();
        }
    }
}

class Message extends Component {
    constructor(
        selector: string,
        private text: string
    ) {
        super(selector);
    }

    render(): string {
        return this.text;
    }
}

class Counter extends Component {
    private count = 0;

    constructor(selector: string) {
        super(selector);
    }

    render(): string {
        return `Count: ${this.count}`;
    }

    increment(): void {
        this.count++;
        this.mount();  // Re-render
    }
}

Repository Pattern mit Vererbung

abstract class BaseRepository<T extends { id: number }> {
    protected items: T[] = [];

    findAll(): T[] {
        return [...this.items];
    }

    findById(id: number): T | undefined {
        return this.items.find(item => item.id === id);
    }

    create(item: T): T {
        this.items.push(item);
        return item;
    }

    delete(id: number): boolean {
        const index = this.items.findIndex(item => item.id === id);
        if (index !== -1) {
            this.items.splice(index, 1);
            return true;
        }
        return false;
    }

    abstract validate(item: T): boolean;
}

interface User {
    id: number;
    name: string;
    email: string;
}

class UserRepository extends BaseRepository<User> {
    validate(user: User): boolean {
        return user.name.length > 0 && user.email.includes("@");
    }

    override create(user: User): User {
        if (!this.validate(user)) {
            throw new Error("Invalid user data");
        }
        return super.create(user);
    }

    findByEmail(email: string): User | undefined {
        return this.items.find(user => user.email === email);
    }
}

Zusammenfassung

  • extends: Klasse von anderer Klasse ableiten
  • super(): Konstruktor der Elternklasse aufrufen
  • super.method(): Eltern-Methode aufrufen
  • override: Methode explizit überschreiben
  • Polymorphismus: Verschiedene Typen, gleiche Schnittstelle
  • instanceof: Type Guard für Klassenhierarchien

Im nächsten Kapitel lernst du abstrakte Klassen!

Zurück zum TypeScript Kurs