← Back to Index

Chapter 2.3: Classes and OOP

Familiar OOP concepts, TypeScript-flavored implementation

📦 Class Basics

TypeScript's class syntax is very similar to Java/C++, but compiles to JavaScript's prototype chain mechanism under the hood.

class Animal {
    name: string;
    sound: string;

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

    speak(): string {
        return `${this.name} says ${this.sound}!`;
    }
}

const cat = new Animal("小猫", "喵");
console.log(cat.speak());  // "小猫 says 喵!"

💡 Key difference from Java/C++:

TypeScript class properties must be declared in the class body or via parameter property shorthand in the constructor. Unlike Java, you can't use undeclared fields directly in the constructor.

🔒 Access Modifiers

TypeScript provides three access modifiers, consistent with Java/C++ concepts.

class BankAccount {
    public owner: string;          // 公开(默认)
    protected balance: number;     // 子类可访问
    private pin: string;           // 仅类内部可访问
    readonly accountId: string;    // 只读,初始化后不可修改

    constructor(owner: string, balance: number, pin: string) {
        this.owner = owner;
        this.balance = balance;
        this.pin = pin;
        this.accountId = crypto.randomUUID();
    }

    public getBalance(): number { return this.balance; }
    private validatePin(input: string): boolean { return input === this.pin; }

    public withdraw(amount: number, pin: string): boolean {
        if (!this.validatePin(pin) || amount > this.balance) return false;
        this.balance -= amount;
        return true;
    }
}

ES native private fields (truly isolated at runtime):

class SecureVault {
    #secret: string;  // 真正的私有字段,运行时也无法从外部访问

    constructor(secret: string) {
        this.#secret = secret;
    }

    reveal(): string {
        return this.#secret;
    }
}

const vault = new SecureVault("密码");
// vault.#secret;  // ❌ 语法错误:运行时和编译时都不允许

🔄 Comparison with Java:

TS's private is a compile-time check — at runtime you can still access it via (obj as any).pin. For runtime isolation, use the #field syntax (ES2022 private fields).

🔄 Comparison with Python:

Python uses _ (convention-private) and __ (name mangling) to indicate access levels. TS has explicit public/private/protected keywords with strict compile-time checks.

✨ Parameter Property Shorthand

This is TypeScript-specific syntactic sugar — adding an access modifier before a constructor parameter automatically declares and assigns a property of the same name.

// ❌ 传统写法需要:声明属性 → 构造函数参数 → 手动赋值(三步)
// ✅ 参数属性简写:一步到位
class User {
    constructor(
        public name: string,
        private age: number,
        readonly email: string,
    ) {}

    getAge(): number {
        return this.age;
    }
}

💡 Practical advice:

Parameter property shorthand is extremely common in simple data classes. But if the constructor has complex logic (like parameter validation), mixing shorthand with manual assignment may reduce readability.

🧬 Inheritance

Use the extends keyword for class inheritance, consistent with Java's single inheritance model.

class Animal {
    constructor(public name: string, protected sound: string) {}
    speak(): string { return `${this.name}: ${this.sound}`; }
}

class Dog extends Animal {
    constructor(name: string, public breed: string) {
        super(name, "汪汪");  // 必须调用 super()
    }

    speak(): string {
        return `🐕 ${super.speak()}!(品种:${this.breed})`;
    }

    fetch(item: string): string {
        return `${this.name} 捡回了 ${item}`;
    }
}

const dog = new Dog("旺财", "金毛");
console.log(dog.speak());   // "🐕 旺财: 汪汪!(品种:金毛)"
console.log(dog.fetch("飞盘"));  // "旺财 捡回了 飞盘"

🎨 Abstract Classes

Abstract classes cannot be instantiated directly and are used to define template methods that subclasses must implement.

abstract class Shape {
    abstract area(): number;
    abstract perimeter(): number;

    describe(): string {
        return `面积: ${this.area().toFixed(2)}, 周长: ${this.perimeter().toFixed(2)}`;
    }
}

class Circle extends Shape {
    constructor(private radius: number) { super(); }
    area(): number { return Math.PI * this.radius ** 2; }
    perimeter(): number { return 2 * Math.PI * this.radius; }
}

class Rectangle extends Shape {
    constructor(private width: number, private height: number) { super(); }
    area(): number { return this.width * this.height; }
    perimeter(): number { return 2 * (this.width + this.height); }
}

// const s = new Shape();        // ❌ 不能实例化抽象类
const circle = new Circle(5);
console.log(circle.describe());  // "面积: 78.54, 周长: 31.42"

🔌 Interface Implementation

Use implements to explicitly declare that a class follows an interface contract. A class can implement multiple interfaces.

interface Serializable {
    serialize(): string;
}

interface Printable {
    print(): void;
}

class Document implements Serializable, Printable {
    constructor(public title: string, public content: string) {}

    serialize(): string {
        return JSON.stringify({ title: this.title, content: this.content });
    }

    print(): void {
        console.log(`=== ${this.title} ===\n${this.content}`);
    }
}

// 接口约束函数参数
function save(item: Serializable): void {
    const data = item.serialize();
    console.log("保存数据:", data);
}

const doc = new Document("笔记", "TypeScript 很棒");
save(doc);

💡 extends vs implements:

  • extends: Inherits a class and gets its implementation code
  • implements: Declares adherence to an interface contract, must implement all methods yourself
  • Both can be used together: class A extends B implements C, D

⚡ Static Members

Static properties and methods belong to the class itself, not instances. Commonly used for factory patterns and utility methods.

class IdGenerator {
    private static nextId: number = 1;

    static generate(): number {
        return IdGenerator.nextId++;
    }

    static reset(): void {
        IdGenerator.nextId = 1;
    }
}

console.log(IdGenerator.generate());  // 1
console.log(IdGenerator.generate());  // 2

// 单例模式
class Config {
    private static instance: Config | null = null;
    private constructor(public readonly env: string) {}

    static getInstance(): Config {
        if (!Config.instance) {
            Config.instance = new Config(process.env.NODE_ENV ?? "development");
        }
        return Config.instance;
    }
}

🎭 Introduction to Decorators

Decorators are a special syntax used to enhance class and method behavior without modifying the original code. Widely used in frameworks like NestJS and Angular.

// 方法装饰器:自动记录方法调用日志
function log(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    const original = descriptor.value;
    descriptor.value = function (...args: any[]) {
        console.log(`调用 ${propertyKey}(${args.join(", ")})`);
        const result = original.apply(this, args);
        console.log(`${propertyKey} 返回: ${result}`);
        return result;
    };
}

class UserService {
    @log
    findById(id: number): string {
        return `User_${id}`;
    }
}

const service = new UserService();
service.findById(42);
// 输出:
// 调用 findById(42)
// findById 返回: User_42

⚠️ Notes:

TypeScript 5.0+ supports the TC39 Stage 3 decorator standard. Legacy experimental decorators require "experimentalDecorators": true in tsconfig.json. NestJS and Angular currently still use the legacy syntax.

📝 Chapter Summary

Class Basics

Property declarations, constructors, methods — syntax similar to Java/C++, compiles to JS prototype chain

Access Modifiers

public/private/protected/readonly; #field provides runtime isolation

Parameter Property Shorthand

Constructor parameters with modifiers auto-declare + assign — TypeScript-exclusive syntactic sugar

Inheritance and Abstract Classes

extends single inheritance; abstract defines template methods that subclasses must implement

Interface Implementation

implements declares contracts, supports multiple interface implementation

Static Members and Decorators

Static members for factory/singleton patterns; decorators enhance class behavior, widely used in frameworks