Zum Inhalt springen
TypeScript Anfänger 20 min

Access Modifiers

Lerne die Zugriffsmodifikatoren public, private und protected in TypeScript-Klassen.

Aktualisiert:

Access Modifiers

Access Modifiers (Zugriffsmodifikatoren) kontrollieren die Sichtbarkeit von Properties und Methoden in Klassen. TypeScript bietet drei Modifikatoren: public, private und protected.

Public

Standard-Modifier - überall zugänglich:

class User {
    public name: string;      // Explizit public
    email: string;            // Implizit public

    constructor(name: string, email: string) {
        this.name = name;
        this.email = email;
    }

    public greet(): string {  // Explizit public
        return `Hallo, ${this.name}!`;
    }
}

const user = new User("Max", "max@example.com");
console.log(user.name);     // OK
console.log(user.email);    // OK
console.log(user.greet());  // OK

Private

Nur innerhalb der Klasse zugänglich:

class BankAccount {
    private balance: number = 0;

    constructor(private accountNumber: string) {}

    deposit(amount: number): void {
        if (amount > 0) {
            this.balance += amount;
        }
    }

    withdraw(amount: number): boolean {
        if (amount > 0 && amount <= this.balance) {
            this.balance -= amount;
            return true;
        }
        return false;
    }

    getBalance(): number {
        return this.balance;
    }
}

const account = new BankAccount("DE123456");
account.deposit(100);
console.log(account.getBalance());  // 100

// Fehler: Property 'balance' is private
// console.log(account.balance);

JavaScript Private Fields (#)

TypeScript unterstützt auch native JavaScript Private Fields:

class User {
    #password: string;

    constructor(public username: string, password: string) {
        this.#password = password;
    }

    checkPassword(input: string): boolean {
        return this.#password === input;
    }
}

const user = new User("max", "secret123");
console.log(user.checkPassword("secret123"));  // true

// Echte Runtime-Privacy!
// console.log(user.#password);  // Syntax Error

private vs

class Example {
    private tsPrivate = "TypeScript private";
    #jsPrivate = "JavaScript private";

    showDifference(): void {
        console.log(this.tsPrivate);  // OK
        console.log(this.#jsPrivate); // OK
    }
}

const ex = new Example();

// TypeScript private: Compile-Zeit Fehler, aber zur Laufzeit zugänglich
// (ex as any).tsPrivate;  // Funktioniert zur Laufzeit!

// JavaScript private: Echte Runtime-Privacy
// (ex as any).#jsPrivate;  // Syntax Error!

Protected

Innerhalb der Klasse und in abgeleiteten Klassen zugänglich:

class Animal {
    protected name: string;

    constructor(name: string) {
        this.name = name;
    }

    protected makeSound(): string {
        return "Some sound";
    }
}

class Dog extends Animal {
    constructor(name: string, private breed: string) {
        super(name);
    }

    bark(): string {
        // Zugriff auf protected Members
        return `${this.name} says: Woof!`;
    }

    describe(): string {
        // Kann auch protected Methoden aufrufen
        return `${this.name} is a ${this.breed} and ${this.makeSound()}`;
    }
}

const dog = new Dog("Buddy", "Golden Retriever");
console.log(dog.bark());     // OK
console.log(dog.describe()); // OK

// Fehler: Property 'name' is protected
// console.log(dog.name);

Zusammenfassung der Modifier

ModifierKlasseSubklasseAußerhalb
public
protected
private
# (JS private)

Readonly kombinieren

class Config {
    constructor(
        public readonly apiUrl: string,
        private readonly apiKey: string,
        protected readonly timeout: number
    ) {}

    getAuthHeader(): string {
        return `Bearer ${this.apiKey}`;
    }
}

const config = new Config("https://api.example.com", "secret", 5000);
console.log(config.apiUrl);  // OK - public readonly

// Fehler: Cannot assign to 'apiUrl'
// config.apiUrl = "other";

Praktische Patterns

Encapsulation Pattern

class User {
    private _email: string;

    constructor(
        public readonly id: number,
        public name: string,
        email: string
    ) {
        this._email = email;
    }

    get email(): string {
        return this._email;
    }

    set email(value: string) {
        if (!this.isValidEmail(value)) {
            throw new Error("Invalid email format");
        }
        this._email = value;
    }

    private isValidEmail(email: string): boolean {
        return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
    }
}

const user = new User(1, "Max", "max@example.com");
user.email = "newemail@example.com";  // OK
// user.email = "invalid";  // Throws Error

State Machine

class TrafficLight {
    private currentState: "red" | "yellow" | "green" = "red";

    private transitions: Record<string, "red" | "yellow" | "green"> = {
        "red": "green",
        "green": "yellow",
        "yellow": "red"
    };

    getState(): string {
        return this.currentState;
    }

    next(): void {
        this.currentState = this.transitions[this.currentState];
    }
}

const light = new TrafficLight();
console.log(light.getState()); // "red"
light.next();
console.log(light.getState()); // "green"
light.next();
console.log(light.getState()); // "yellow"

Builder Pattern

class RequestBuilder {
    private url: string = "";
    private method: string = "GET";
    private headers: Record<string, string> = {};
    private body?: string;

    setUrl(url: string): this {
        this.url = url;
        return this;
    }

    setMethod(method: string): this {
        this.method = method;
        return this;
    }

    addHeader(key: string, value: string): this {
        this.headers[key] = value;
        return this;
    }

    setBody(body: object): this {
        this.body = JSON.stringify(body);
        this.headers["Content-Type"] = "application/json";
        return this;
    }

    build(): Request {
        return new Request(this.url, {
            method: this.method,
            headers: this.headers,
            body: this.body
        });
    }
}

const request = new RequestBuilder()
    .setUrl("https://api.example.com/users")
    .setMethod("POST")
    .addHeader("Authorization", "Bearer token")
    .setBody({ name: "Max", email: "max@example.com" })
    .build();

Singleton Pattern

class Database {
    private static instance: Database;
    private connected: boolean = false;

    private constructor() {}

    static getInstance(): Database {
        if (!Database.instance) {
            Database.instance = new Database();
        }
        return Database.instance;
    }

    connect(): void {
        if (!this.connected) {
            console.log("Connecting to database...");
            this.connected = true;
        }
    }

    query(sql: string): void {
        if (!this.connected) {
            throw new Error("Not connected to database");
        }
        console.log(`Executing: ${sql}`);
    }
}

const db1 = Database.getInstance();
const db2 = Database.getInstance();
console.log(db1 === db2);  // true - gleiche Instanz

db1.connect();
db1.query("SELECT * FROM users");

Best Practices

  1. Mache Properties standardmäßig private

    class User {
        private email: string;  // Gut
        // email: string;       // Vermeiden
    }
  2. Nutze Getter/Setter für kontrollierte Zugriffe

    class Account {
        private _balance: number = 0;
    
        get balance(): number {
            return this._balance;
        }
    }
  3. Verwende readonly für unveränderliche Properties

    class User {
        constructor(public readonly id: number) {}
    }
  4. Protected nur bei Vererbungshierarchien

    class BaseService {
        protected logger: Logger;
    }

Zusammenfassung

  • public: Überall zugänglich (Standard)
  • private: Nur in der Klasse
  • protected: Klasse und Subklassen
  • #: JavaScript native private (Runtime-Privacy)
  • readonly: Einmal setzen, dann unveränderlich

Im nächsten Kapitel lernst du Vererbung in TypeScript!

Zurück zum TypeScript Kurs