第5章 面向对象编程
本章定位:深入学习TypeScript的面向对象编程(OOP),包括类的定义、构造函数、访问修饰符、继承、多态、接口实现等,并与Python的类系统进行对比。
5.1 类(class)的定义
Section titled “5.1 类(class)的定义”class User { // 属性 name: string; age: number; email: string = "";
// 构造函数 constructor(name: string, age: number) { this.name = name; this.age = age; }
// 方法 greet(): string { return `Hello, I'm ${this.name}`; }
// 静态属性 static MAX_AGE = 120;
// 静态方法 static isValidAge(age: number): boolean { return age >= 0 && age <= this.MAX_AGE; }}
const user = new User("Alice", 30);console.log(user.greet()); // "Hello, I'm Alice"console.log(User.isValidAge(30)); // truePython对比:
class User: MAX_AGE = 120
def __init__(self, name: str, age: int): self.name = name self.age = age self.email = ""
def greet(self) -> str: return f"Hello, I'm {self.name}"
@classmethod def is_valid_age(cls, age: int) -> bool: return 0 <= age <= cls.MAX_AGE
user = User("Alice", 30)print(user.greet()) # "Hello, I'm Alice"print(User.is_valid_age(30)) # TrueTypeScript类的特点
Section titled “TypeScript类的特点”- 类型注解:属性和方法需要类型注解
- 访问修饰符:控制属性和方法的可见性
- 参数属性:在构造函数参数前加修饰符,自动创建属性
- 只读属性:用
readonly标记不可修改的属性
class User { // 参数属性:public name会自动创建name属性 constructor( public name: string, public age: number, private password: string // private不在类外可见 ) {}
// 只读属性 readonly id: string;
// 方法 getInfo(): string { return `${this.name}, ${this.age}`; }}
const user = new User("Alice", 30, "secret");console.log(user.name); // "Alice"console.log(user.age); // 30console.log(user.password); // 编译错误!private类的类型注解
Section titled “类的类型注解”class Point { x: number; y: number;}
// 类本身就是类型const p: Point = new Point();p.x = 10;p.y = 20;
// 类实例的类型function printPoint(p: Point): void { console.log(`(${p.x}, ${p.y})`);}class Animal { constructor(public name: string) {}
move(distance: number = 0): void { console.log(`${this.name} moved ${distance}m`); }}
class Dog extends Animal { breed: string;
constructor(name: string, breed: string) { super(name); // 调用父类构造函数 this.breed = breed; }
// 重写父类方法 move(distance: number = 5): void { console.log(`${this.name} (${this.breed}) runs ${distance}m`); }
// 新方法 bark(): void { console.log("Woof! Woof!"); }}
const dog = new Dog("Buddy", "Golden Retriever");dog.move(); // "Buddy (Golden Retriever) runs 5m"dog.bark(); // "Woof! Woof!"Python对比:
class Animal: def __init__(self, name: str): self.name = name
def move(self, distance: int = 0): print(f"{self.name} moved {distance}m")
class Dog(Animal): def __init__(self, name: str, breed: str): super().__init__(name) self.breed = breed
def move(self, distance: int = 5): print(f"{self.name} ({self.breed}) runs {distance}m")
def bark(self): print("Woof! Woof!")5.2 构造函数与属性初始化
Section titled “5.2 构造函数与属性初始化”构造函数重载
Section titled “构造函数重载”class Product { name: string; price: number; category: string;
// 多个构造函数签名 constructor(name: string, price: number, category: string); constructor(name: string, price: number); constructor(name: string);
// 实现 constructor(name: string, price?: number, category?: string) { this.name = name; this.price = price ?? 0; this.category = category ?? "uncategorized"; }}
const p1 = new Product("Apple", 1.5, "fruit");const p2 = new Product("Book", 20);const p3 = new Product("Item");属性初始化器
Section titled “属性初始化器”class User { name: string = "Anonymous"; age: number = 0; createdAt: Date = new Date();
// 可以直接赋值 isActive: boolean = true;}严格属性初始化检查
Section titled “严格属性初始化检查”// tsconfig.json: strictPropertyInitialization: true
class User { name: string; // 编译错误!必须有初始值或在构造函数中初始化
constructor(name: string) { this.name = name; // OK }}
// 或者使用确定赋值断言class User2 { name!: string; // 跳过初始化检查
setName(name: string) { this.name = name; }}私有构造函数
Section titled “私有构造函数”class Singleton { private static instance: Singleton;
private constructor() {}
static getInstance(): Singleton { if (!Singleton.instance) { Singleton.instance = new Singleton(); } return Singleton.instance; }}
const instance = Singleton.getInstance();// const instance2 = new Singleton(); // 编译错误!构造函数是私有的5.3 访问修饰符:public、private、protected
Section titled “5.3 访问修饰符:public、private、protected”public(默认)
Section titled “public(默认)”所有成员默认是public,可以在任何地方访问:
class User { public name: string; // 可省略public public age: number;
constructor(name: string, age: number) { this.name = name; this.age = age; }
public greet(): string { return `Hello, ${this.name}`; }}
const user = new User("Alice", 30);console.log(user.name); // "Alice"(可在类外访问)console.log(user.greet()); // "Hello, Alice"(可在类外访问)private
Section titled “private”私有成员只能在类内部访问:
class BankAccount { public accountNumber: string; private balance: number;
constructor(accountNumber: string, initialBalance: number) { this.accountNumber = accountNumber; this.balance = initialBalance; }
public deposit(amount: number): void { if (amount > 0) { this.balance += amount; } }
public withdraw(amount: number): boolean { if (amount > 0 && amount <= this.balance) { this.balance -= amount; return true; } return false; }
public getBalance(): number { return this.balance; // 只能在类内部访问 }}
const account = new BankAccount("12345", 1000);console.log(account.accountNumber); // "12345"console.log(account.balance); // 编译错误!privateconsole.log(account.getBalance()); // 1000Python对比:
# Python约定使用下划线前缀表示私有(但不是强制的)class BankAccount: def __init__(self, account_number: str, initial_balance: float): self.account_number = account_number self._balance = initial_balance # 约定私有
def get_balance(self) -> float: return self._balance
# Python的"私有"只是约定,可以强制访问account = BankAccount("12345", 1000)print(account._balance) # 可以访问,只是不符合PEP8protected
Section titled “protected”受保护成员可以在类本身及其子类中访问:
class Animal { protected name: string;
constructor(name: string) { this.name = name; }
protected makeSound(): void { console.log("Some sound"); }}
class Dog extends Animal { private breed: string;
constructor(name: string, breed: string) { super(name); this.breed = breed; }
public speak(): void { console.log(`${this.name} says:`); // 可访问protected的name this.makeSound(); // 可访问protected的makeSound }}
const dog = new Dog("Buddy", "Golden");console.log(dog.name); // 编译错误!protecteddog.speak(); // OK,输出 "Buddy says: Some sound"访问修饰符对比表
Section titled “访问修饰符对比表”| 修饰符 | 类内 | 子类 | 类外 |
|---|---|---|---|
| public | ✓ | ✓ | ✓ |
| protected | ✓ | ✓ | ✗ |
| private | ✓ | ✗ | ✗ |
5.4 只读属性与抽象类
Section titled “5.4 只读属性与抽象类”readonly 修饰符
Section titled “readonly 修饰符”只读属性只能在声明时或构造函数中赋值,之后不能修改:
class User { readonly id: string; name: string;
constructor(id: string, name: string) { this.id = id; // OK,在构造函数中赋值 this.name = name; }
setName(name: string) { this.name = name; this.id = "new-id"; // 编译错误!readonly }}
const user = new User("123", "Alice");user.name = "Bob"; // OKuser.id = "456"; // 编译错误!readonlyreadonly 与 const
Section titled “readonly 与 const”// readonly用于类的属性// const用于变量
class Config { readonly API_URL = "https://api.example.com"; readonly MAX_RETRIES = 3;
constructor(readonly env: string) { // 参数属性可以直接标记readonly }}
const config = new Config("production");config.API_URL = "http://other.com"; // 编译错误!抽象类不能直接实例化,只能作为基类被继承:
abstract class Shape { abstract name: string;
// 抽象方法:子类必须实现 abstract area(): number; abstract perimeter(): number;
// 具体方法 describe(): string { return `This is a ${this.name}`; }}
// 不能直接实例化// const shape = new Shape(); // 编译错误!
class Circle extends Shape { constructor(public radius: number) { super(); }
get name(): string { return "circle"; }
area(): number { return Math.PI * this.radius ** 2; }
perimeter(): number { return 2 * Math.PI * this.radius; }}
class Rectangle extends Shape { constructor(public width: number, public height: number) { super(); }
get name(): string { return "rectangle"; }
area(): number { return this.width * this.height; }
perimeter(): number { return 2 * (this.width + this.height); }}
const circle = new Circle(5);console.log(circle.area()); // 78.54console.log(circle.describe()); // "This is a circle"Python对比:
from abc import ABC, abstractmethod
class Shape(ABC): @property @abstractmethod def name(self) -> str: pass
@abstractmethod def area(self) -> float: pass
@abstractmethod def perimeter(self) -> float: pass
def describe(self) -> str: return f"This is a {self.name}"
class Circle(Shape): def __init__(self, radius: float): self.radius = radius
@property def name(self) -> str: return "circle"
def area(self) -> float: return math.pi * self.radius ** 25.5 继承与多态
Section titled “5.5 继承与多态”子类可以重写父类的方法:
class Base { greet(): string { return "Hello!"; }}
class Derived extends Base { // 重写父类方法 greet(): string { return "Hi there!"; }
// 扩展父类方法 greetFormal(): string { return `Good day, ${super.greet()}`; }}
const derived = new Derived();console.log(derived.greet()); // "Hi there!"console.log(derived.greetFormal()); // "Good day, Hi there!"super 关键字
Section titled “super 关键字”使用super访问父类的属性和方法:
class Animal { constructor(public name: string) {}
move(): void { console.log(`${this.name} is moving`); }}
class Bird extends Animal { constructor(name: string, public wingSpan: number) { super(name); // 调用父类构造函数 }
move(): void { console.log(`${this.name} is flying`); super.move(); // 调用父类方法 }}父类引用可以指向子类对象:
class Animal { name: string;
constructor(name: string) { this.name = name; }
speak(): string { return "Some sound"; }}
class Dog extends Animal { speak(): string { return "Woof!"; }}
class Cat extends Animal { speak(): string { return "Meow!"; }}
function makeSpeak(animal: Animal): void { console.log(`${animal.name} says: ${animal.speak()}`);}
const animals: Animal[] = [ new Dog("Buddy"), new Cat("Whiskers"), new Animal("Generic")];
animals.forEach(makeSpeak);// "Buddy says: Woof!"// "Whiskers says: Meow!"// "Generic says: Some sound"Python对比:
class Animal: def __init__(self, name: str): self.name = name
def speak(self) -> str: return "Some sound"
class Dog(Animal): def speak(self) -> str: return "Woof!"
class Cat(Animal): def speak(self) -> str: return "Meow!"
def make_speak(animal: Animal) -> None: print(f"{animal.name} says: {animal.speak()}")
animals = [Dog("Buddy"), Cat("Whiskers"), Animal("Generic")]for animal in animals: make_speak(animal)5.6 接口实现(implements)
Section titled “5.6 接口实现(implements)”使用implements关键字让类实现接口:
interface Printable { print(): void;}
interface Serializable { serialize(): string;}
class Report implements Printable, Serializable { constructor(public title: string, public content: string) {}
print(): void { console.log(`Report: ${this.title}`); console.log(this.content); }
serialize(): string { return JSON.stringify({ title: this.title, content: this.content }); }}
const report = new Report("Q1 Results", "Sales increased by 20%");report.print();const json = report.serialize();接口约束类结构
Section titled “接口约束类结构”interface User { name: string; age: number; email?: string;}
class UserAccount implements User { name: string; age: number; email: string = "";
constructor(name: string, age: number) { this.name = name; this.age = age; }
// User接口不需要实现email,但类可以有自己的额外属性 greet(): string { return `Hello, I'm ${this.name}`; }}抽象类 vs 接口
Section titled “抽象类 vs 接口”| 特性 | 抽象类 | 接口 |
|---|---|---|
| 属性 | 可以有属性 | 只能有方法签名 |
| 方法实现 | 可以有实现 | 默认无实现(TS 4.8+可默认实现) |
| 多继承 | 不能 | 可以实现多个 |
| 构造函数 | 可以有 | 不能有 |
| 访问修饰符 | 可以有 | 方法默认public |
// 抽象类:适合有共享实现的情况abstract class Base { abstract doSomething(): void;
log(): void { console.log("Logging..."); }}
// 接口:适合定义契约interface Reader { read(): string;}
interface Writer { write(content: string): void;}5.7 静态成员(static)
Section titled “5.7 静态成员(static)”属于类本身,而不是实例:
class MathUtils { // 静态属性 static PI = 3.14159;
// 静态方法 static circleArea(radius: number): number { return this.PI * radius * radius; }
static circleCircumference(radius: number): number { return 2 * this.PI * radius; }}
console.log(MathUtils.PI); // 3.14159console.log(MathUtils.circleArea(5)); // 78.54console.log(MathUtils.circleCircumference(5)); // 31.42
// 无需创建实例const area = MathUtils.circleArea(10);静态构造函数
Section titled “静态构造函数”静态属性初始化:
class Configuration { static instance: Configuration; static readonly DEFAULT_TIMEOUT = 30; static readonly DEFAULT_PORT = 8080;
timeout: number; port: number;
private constructor() { this.timeout = Configuration.DEFAULT_TIMEOUT; this.port = Configuration.DEFAULT_PORT; }
static getInstance(): Configuration { if (!Configuration.instance) { Configuration.instance = new Configuration(); } return Configuration.instance; }}Python对比:
class Configuration: DEFAULT_TIMEOUT = 30 DEFAULT_PORT = 8080
def __init__(self): self.timeout = self.DEFAULT_TIMEOUT self.port = self.DEFAULT_PORT
@classmethod def get_instance(cls): if not hasattr(cls, '_instance'): cls._instance = cls() return cls._instance静态成员的可见性
Section titled “静态成员的可见性”class Counter { private static count = 0;
static increment(): number { return ++Counter.count; }
static getCount(): number { return Counter.count; }}
console.log(Counter.getCount()); // 0Counter.increment();Counter.increment();console.log(Counter.getCount()); // 25.8 Python类 vs TypeScript类
Section titled “5.8 Python类 vs TypeScript类”语法对比总览
Section titled “语法对比总览”| Python | TypeScript | 说明 |
|---|---|---|
class Foo: | class Foo {} | 类定义 |
self | this | 引用自身 |
def __init__(self, x): | constructor(x: type) {} | 构造函数 |
def method(self): | method(): void {} | 实例方法 |
@staticmethod | static method(): void {} | 静态方法 |
@classmethod | static method(): void {} (调用方式不同) | 类方法 |
_private | private | 私有(Python是约定) |
__slot__ | 无对应 | 属性限制 |
@property | get property(): type {} | 属性装饰器 |
构造函数对比
Section titled “构造函数对比”# Pythonclass User: def __init__(self, name: str, age: int, email: str = ""): self.name = name self.age = age self.email = email// TypeScriptclass User { constructor( public name: string, public age: number, public email: string = "" ) {}}# Pythonclass Animal: def __init__(self, name: str): self.name = name
def speak(self) -> str: return "..."
class Dog(Animal): def __init__(self, name: str, breed: str): super().__init__(name) self.breed = breed
def speak(self) -> str: return "Woof!"// TypeScriptclass Animal { constructor(public name: string) {}
speak(): string { return "..."; }}
class Dog extends Animal { constructor(name: string, public breed: string) { super(name); }
speak(): string { return "Woof!"; }}访问控制对比
Section titled “访问控制对比”# Python:约定大于强制class BankAccount: def __init__(self, balance: float): self._balance = balance # 私有约定 self.__private = 0 # 名称重整(name mangling)
def get_balance(self): return self._balance
# 从外部仍可访问(只是不符合约定)account = BankAccount(100)print(account._balance) # 100,可以访问但不符合PEP8print(account._BankAccount__private) # 0,名称重整// TypeScript:强制访问控制class BankAccount { private balance: number;
constructor(balance: number) { this.balance = balance; }
getBalance(): number { return this.balance; }}
const account = new BankAccount(100);console.log(account.balance); // 编译错误!private属性装饰器对比
Section titled “属性装饰器对比”# Python @propertyclass User: @property def full_name(self) -> str: return f"{self.first_name} {self.last_name}"
@full_name.setter def full_name(self, value: str): first, last = value.split() self.first_name = first self.last_name = last// TypeScript getter/setterclass User { private _firstName: string = ""; private _lastName: string = "";
get fullName(): string { return `${this._firstName} ${this._lastName}`; }
set fullName(value: string) { const [first, last] = value.split(" "); this._firstName = first; this._lastName = last; }}# Python dataclassfrom dataclasses import dataclass
@dataclassclass User: name: str age: int email: str = ""
def greet(self) -> str: return f"Hello, {self.name}"// TypeScript:手写或用库// 手写方式class User { constructor( public name: string, public age: number, public email: string = "" ) {}
greet(): string { return `Hello, ${this.name}`; }}5.9 装饰器模式
Section titled “5.9 装饰器模式”装饰器增强现有类的功能:
class User { name: string;
constructor(name: string) { this.name = name; }
greet(): string { return `Hello, ${this.name}`; }}
// 装饰器函数function withLogging<T extends { greet(): string }>(target: T): T & { log(): void } { const originalGreet = target.greet.bind(target);
target.greet = function(): string { console.log(`[LOG] Calling greet for ${this.name}`); return originalGreet(); } as typeof originalGreet;
return target as T & { log(): void };}
const user = withLogging(new User("Alice"));console.log(user.greet()); // [LOG] Calling greet for Alice + Hello, Alice类装饰器工厂
Section titled “类装饰器工厂”创建带参数的装饰器:
function sealed(target: Function): void { Object.seal(target); Object.seal(target.prototype);}
function timeout(ms: number) { return function( target: any, propertyKey: string, descriptor: PropertyDescriptor ) { const original = descriptor.value;
descriptor.value = function(...args: any[]) { setTimeout(() => { original.apply(this, args); }, ms); };
return descriptor; };}
class DataService { @timeout(1000) fetchData(): void { console.log("Fetching data..."); }}function readonly( target: any, propertyKey: string, descriptor: PropertyDescriptor): PropertyDescriptor { descriptor.writable = false; return descriptor;}
class Point { constructor(public x: number, public y: number) {}
@readonly distance(): number { return Math.sqrt(this.x ** 2 + this.y ** 2); }}
const p = new Point(3, 4);console.log(p.distance()); // 5// p.distance = () => 0; // 编译错误!readonlyPython对比:
# Python装饰器import functools
def log_calls(func): @functools.wraps(func) def wrapper(*args, **kwargs): print(f"Calling {func.__name__}") return func(*args, **kwargs) return wrapper
class Calculator: @log_calls def add(self, a: int, b: int) -> int: return a + b5.10 本章小结
Section titled “5.10 本章小结”本章学习了TypeScript的面向对象编程:
-
类定义:
- 使用
class关键字 - 构造函数是
constructor - 参数属性用修饰符简化
- 使用
-
访问修饰符:
public:到处可访问protected:类及子类可访问private:仅类内可访问readonly:只读
-
继承与多态:
- 使用
extends继承 super()调用父类构造函数- 方法可以重写
- 使用
-
接口实现:
implements关键字- 一个类可实现多个接口
-
静态成员:
static关键字- 属于类本身,不是实例
-
Python对比:
- Python用
self,TypeScript用this - Python访问控制是约定,TypeScript是强制的
- TypeScript有明确的
public/private/protected
- Python用
练习5.1:创建一个类
Section titled “练习5.1:创建一个类”定义一个BankAccount类,包含:
- 属性:
accountNumber(只读)、balance - 方法:
deposit(amount)、withdraw(amount) - 验证余额不能为负
练习5.2:继承与多态
Section titled “练习5.2:继承与多态”创建Shape抽象类及其子类Circle和Rectangle,实现多态计算面积。
练习5.3:接口实现
Section titled “练习5.3:接口实现”创建Sortable接口,实现sort()方法。让Person类实现该接口。
练习5.4:静态成员
Section titled “练习5.4:静态成员”创建IdGenerator类,使用静态方法生成唯一ID。
练习5.5:装饰器
Section titled “练习5.5:装饰器”创建一个@debounce装饰器,延迟方法执行。
下一章我们将学习TypeScript的高级类型,包括泛型约束、条件类型、映射类型、工具类型等,掌握类型级别的编程技巧。