Abstrakte Klassen
Lerne abstrakte Klassen und Methoden in TypeScript für flexible Basisklassen-Designs.
Aktualisiert:
Abstrakte Klassen
Abstrakte Klassen sind Klassen, die nicht direkt instanziiert werden können. Sie dienen als Vorlage für andere Klassen und können sowohl implementierte als auch abstrakte Methoden enthalten.
Grundlagen
// Abstrakte Klasse
abstract class Animal {
constructor(public name: string) {}
// Implementierte Methode
move(): string {
return `${this.name} is moving`;
}
// Abstrakte Methode - muss in Subklassen implementiert werden
abstract speak(): string;
}
// Kann nicht instanziiert werden:
// const animal = new Animal("Generic"); // Fehler!
// Konkrete Klasse
class Dog extends Animal {
// Muss speak() implementieren
speak(): string {
return `${this.name} says: Woof!`;
}
}
class Cat extends Animal {
speak(): string {
return `${this.name} says: Meow!`;
}
}
const dog = new Dog("Buddy");
console.log(dog.speak()); // "Buddy says: Woof!"
console.log(dog.move()); // "Buddy is moving"
Abstrakte Properties
abstract class Shape {
abstract readonly name: string;
abstract color: string;
abstract getArea(): number;
describe(): string {
return `A ${this.color} ${this.name}`;
}
}
class Rectangle extends Shape {
readonly name = "rectangle";
constructor(
public color: string,
public width: number,
public height: number
) {
super();
}
getArea(): number {
return this.width * this.height;
}
}
class Circle extends Shape {
readonly name = "circle";
constructor(
public color: string,
public radius: number
) {
super();
}
getArea(): number {
return Math.PI * this.radius ** 2;
}
}
const shapes: Shape[] = [
new Rectangle("blue", 10, 5),
new Circle("red", 7)
];
shapes.forEach(shape => {
console.log(shape.describe(), "- Area:", shape.getArea().toFixed(2));
});
Abstrakte Getter und Setter
abstract class Component {
abstract get isVisible(): boolean;
abstract set visible(value: boolean);
abstract render(): string;
log(): void {
console.log(`Component visible: ${this.isVisible}`);
}
}
class Modal extends Component {
private _visible = false;
get isVisible(): boolean {
return this._visible;
}
set visible(value: boolean) {
this._visible = value;
console.log(`Modal is now ${value ? "visible" : "hidden"}`);
}
render(): string {
return `<Modal visible=${this._visible}>`;
}
}
const modal = new Modal();
modal.visible = true;
console.log(modal.render());
Template Method Pattern
Ein klassisches Design Pattern mit abstrakten Klassen:
abstract class DataProcessor {
// Template Method - definiert den Algorithmus
process(data: string): string {
const validated = this.validate(data);
const transformed = this.transform(validated);
const formatted = this.format(transformed);
return formatted;
}
// Abstrakte Schritte - müssen implementiert werden
protected abstract validate(data: string): string;
protected abstract transform(data: string): string;
// Optionaler Schritt mit Default-Implementierung
protected format(data: string): string {
return data.trim();
}
}
class JsonProcessor extends DataProcessor {
protected validate(data: string): string {
JSON.parse(data); // Throws if invalid
return data;
}
protected transform(data: string): string {
const obj = JSON.parse(data);
return JSON.stringify(obj, null, 2);
}
}
class CsvProcessor extends DataProcessor {
protected validate(data: string): string {
if (!data.includes(",")) {
throw new Error("Invalid CSV");
}
return data;
}
protected transform(data: string): string {
return data.split(",").map(v => v.trim()).join(";");
}
protected override format(data: string): string {
return `CSV: ${super.format(data)}`;
}
}
const jsonProcessor = new JsonProcessor();
const csvProcessor = new CsvProcessor();
console.log(jsonProcessor.process('{"name":"Max"}'));
console.log(csvProcessor.process("a, b, c"));
Factory Method Pattern
abstract class Creator {
abstract createProduct(): Product;
operate(): string {
const product = this.createProduct();
return `Creator produced: ${product.operation()}`;
}
}
interface Product {
operation(): string;
}
class ConcreteCreatorA extends Creator {
createProduct(): Product {
return new ConcreteProductA();
}
}
class ConcreteCreatorB extends Creator {
createProduct(): Product {
return new ConcreteProductB();
}
}
class ConcreteProductA implements Product {
operation(): string {
return "Result of ProductA";
}
}
class ConcreteProductB implements Product {
operation(): string {
return "Result of ProductB";
}
}
function clientCode(creator: Creator): void {
console.log(creator.operate());
}
clientCode(new ConcreteCreatorA()); // "Creator produced: Result of ProductA"
clientCode(new ConcreteCreatorB()); // "Creator produced: Result of ProductB"
Praktische Beispiele
HTTP Client
abstract class HttpClient {
protected baseUrl: string;
constructor(baseUrl: string) {
this.baseUrl = baseUrl;
}
// Abstrakte Methode für die eigentliche Anfrage
protected abstract sendRequest(
method: string,
url: string,
body?: unknown
): Promise<unknown>;
// Konkrete Methoden nutzen die abstrakte Methode
async get<T>(endpoint: string): Promise<T> {
return this.sendRequest("GET", endpoint) as Promise<T>;
}
async post<T>(endpoint: string, data: unknown): Promise<T> {
return this.sendRequest("POST", endpoint, data) as Promise<T>;
}
async put<T>(endpoint: string, data: unknown): Promise<T> {
return this.sendRequest("PUT", endpoint, data) as Promise<T>;
}
async delete(endpoint: string): Promise<void> {
await this.sendRequest("DELETE", endpoint);
}
}
class FetchHttpClient extends HttpClient {
protected async sendRequest(
method: string,
endpoint: string,
body?: unknown
): Promise<unknown> {
const response = await fetch(`${this.baseUrl}${endpoint}`, {
method,
headers: { "Content-Type": "application/json" },
body: body ? JSON.stringify(body) : undefined
});
return response.json();
}
}
// Nutzung
const api = new FetchHttpClient("https://api.example.com");
// const users = await api.get<User[]>("/users");
Validation Framework
abstract class Validator<T> {
protected errors: string[] = [];
abstract validate(value: T): boolean;
getErrors(): string[] {
return [...this.errors];
}
isValid(value: T): boolean {
this.errors = [];
return this.validate(value);
}
protected addError(message: string): void {
this.errors.push(message);
}
}
class EmailValidator extends Validator<string> {
validate(email: string): boolean {
if (!email) {
this.addError("Email is required");
return false;
}
if (!email.includes("@")) {
this.addError("Email must contain @");
return false;
}
return true;
}
}
class AgeValidator extends Validator<number> {
constructor(
private minAge: number,
private maxAge: number
) {
super();
}
validate(age: number): boolean {
if (age < this.minAge) {
this.addError(`Age must be at least ${this.minAge}`);
return false;
}
if (age > this.maxAge) {
this.addError(`Age must be at most ${this.maxAge}`);
return false;
}
return true;
}
}
const emailValidator = new EmailValidator();
console.log(emailValidator.isValid("test")); // false
console.log(emailValidator.getErrors()); // ["Email must contain @"]
const ageValidator = new AgeValidator(18, 100);
console.log(ageValidator.isValid(16)); // false
console.log(ageValidator.getErrors()); // ["Age must be at least 18"]
State Machine
abstract class State {
abstract enter(): void;
abstract exit(): void;
abstract update(): void;
abstract canTransitionTo(state: State): boolean;
}
class IdleState extends State {
enter(): void {
console.log("Entering idle state");
}
exit(): void {
console.log("Exiting idle state");
}
update(): void {
console.log("Idle...");
}
canTransitionTo(state: State): boolean {
return state instanceof WalkingState || state instanceof RunningState;
}
}
class WalkingState extends State {
enter(): void {
console.log("Starting to walk");
}
exit(): void {
console.log("Stopping walk");
}
update(): void {
console.log("Walking...");
}
canTransitionTo(state: State): boolean {
return state instanceof IdleState || state instanceof RunningState;
}
}
class RunningState extends State {
enter(): void {
console.log("Starting to run");
}
exit(): void {
console.log("Stopping run");
}
update(): void {
console.log("Running...");
}
canTransitionTo(state: State): boolean {
return state instanceof IdleState || state instanceof WalkingState;
}
}
class Character {
private currentState: State;
constructor(initialState: State) {
this.currentState = initialState;
this.currentState.enter();
}
transitionTo(newState: State): boolean {
if (this.currentState.canTransitionTo(newState)) {
this.currentState.exit();
this.currentState = newState;
this.currentState.enter();
return true;
}
return false;
}
update(): void {
this.currentState.update();
}
}
Abstract vs Interface
| Feature | Abstract Class | Interface |
|---|---|---|
| Implementierte Methoden | Ja | Nein (meist) |
| Konstruktor | Ja | Nein |
| Mehrfach-Vererbung | Nein | Ja (implements) |
| Private/Protected Members | Ja | Nein |
| Runtime-Code | Ja | Nein |
Zusammenfassung
- abstract class: Kann nicht instanziiert werden
- abstract method: Muss in Subklassen implementiert werden
- Kombinierbar: Abstrakte und konkrete Members
- Template Method: Algorithmus-Skelett mit variablen Teilen
- Factory Method: Objekterzeugung an Subklassen delegieren
Im nächsten Modul lernst du fortgeschrittene Typen wie Type Guards und Mapped Types!