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
| Modifier | Klasse | Subklasse | Auß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
-
Mache Properties standardmäßig private
class User { private email: string; // Gut // email: string; // Vermeiden } -
Nutze Getter/Setter für kontrollierte Zugriffe
class Account { private _balance: number = 0; get balance(): number { return this._balance; } } -
Verwende readonly für unveränderliche Properties
class User { constructor(public readonly id: number) {} } -
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!