面向对象编程(OOP)是 TypeScript 的核心特性之一,它使得代码组织更加结构化且可维护。本文将深入探讨 TypeScript 中类的各个方面,从基础概念到高级应用,帮助你全面理解并有效运用面向对象的设计思想。本文涵盖:
- 访问修饰符 (public、private、protected)和抽象类提供了代码封装和继承的机制
- 静态成员支持类级别的属性和方法,在单例、工厂等设计模式中发挥重要作用
- 接口实现增强了代码的可重用性和类型安全,通过契约定义类必须符合的结构
- 装饰器提供了强大的元编程能力,用于以非侵入式方式扩展类和成员的功能
1. 类的定义与继承
1.1 访问修饰符(public, private, protected)
TypeScript 提供了三种访问修饰符来控制类成员的可见性:
- public: 默认修饰符,表示成员可以在任何地方被访问
- private: 限制成员只能在声明它的类内部访问
- protected: 允许成员在声明它的类及其子类中访问
以下代码展示了这三种访问修饰符的实际使用:
typescript
class Person {
public name: string;
private age: number;
protected address: string;
constructor(name: string, age: number, address: string) {
this.name = name;
this.age = age;
this.address = address;
}
public introduce(): string {
return `我叫 ${this.name},今年 ${this.age} 岁`; // 可以访问 private 成员
}
private getPrivateDetails(): string {
return `年龄:${this.age}`; // 只能在 Person 类内部访问
}
protected getAddress(): string {
return this.address; // 可以在 Person 类及其子类中访问
}
}
class Employee extends Person {
private employeeId: string;
constructor(name: string, age: number, address: string, employeeId: string) {
super(name, age, address);
this.employeeId = employeeId;
}
public getEmployeeInfo(): string {
// this.age 会报错,因为 age 是 Person 类的 private 属性
// return `员工 ${this.name},年龄 ${this.age},地址 ${this.address}`; // 错误
// 但可以访问 protected 成员
return `员工 ${this.name},地址 ${this.getAddress()},ID ${this.employeeId}`;
}
}
const person = new Person("张三", 25, "北京市");
console.log(person.name); // 可以访问:public
// console.log(person.age); // 错误:private
// console.log(person.address); // 错误:protected
console.log(person.introduce()); // 可以访问:public 方法
// console.log(person.getPrivateDetails()); // 错误:private 方法
// console.log(person.getAddress()); // 错误:protected 方法
1.2 属性初始化与构造函数
TypeScript 提供了多种属性初始化方式:
构造函数参数属性
一种简洁的语法,允许在声明构造函数参数时同时声明和初始化类属性
typescript
class User {
// 简洁写法,自动创建并初始化属性
constructor(
public username: string,
private password: string,
protected email: string = "default@example.com" // 带默认值
) {}
getInfo(): string {
return `User: ${this.username}, Email: ${this.email}`;
}
}
const user = new User("admin", "secret");
console.log(user.username); // "admin"
// console.log(user.password); // 错误:private 属性
属性初始化器
直接在类定义中初始化属性
typescript
class Counter {
count = 0; // 属性初始化器
readonly maxValue = 100;
private lastUpdated: Date = new Date();
increment(): void {
this.count++;
this.lastUpdated = new Date();
// this.maxValue++; // 错误:readonly 属性不可修改
}
}
非空断言属性初始化
使用 !
操作符向编译器保证属性会在使用前被初始化
typescript
class AsyncData {
data!: string[]; // 使用非空断言,承诺在使用前会初始化
async loadData(): Promise<void> {
// 模拟异步加载
this.data = await Promise.resolve(["item1", "item2"]);
}
// 如果在调用 processData 前没有调用 loadData,可能会导致运行时错误
processData(): number {
return this.data.length;
}
}
1.3 抽象类与方法
抽象类作为其他类的基类,不能被直接实例化,常用于定义通用接口和部分实现:
typescript
abstract class Shape {
constructor(protected color: string) {}
// 抽象方法必须在派生类中实现
abstract calculateArea(): number;
// 具体方法可以被继承或重写
getColor(): string {
return this.color;
}
}
class Circle extends Shape {
constructor(
color: string,
private radius: number
) {
super(color);
}
// 实现抽象方法
calculateArea(): number {
return Math.PI * this.radius * this.radius;
}
// 添加特定方法
getDiameter(): number {
return this.radius * 2;
}
}
class Rectangle extends Shape {
constructor(
color: string,
private width: number,
private height: number
) {
super(color);
}
calculateArea(): number {
return this.width * this.height;
}
}
// const shape = new Shape("red"); // 错误:无法创建抽象类的实例
const circle = new Circle("blue", 5);
const rectangle = new Rectangle("green", 4, 6);
console.log(circle.calculateArea()); // 78.54...
console.log(rectangle.calculateArea()); // 24
console.log(circle.getColor()); // "blue"
2. 静态属性与方法
静态成员属于类本身而不是类的实例,无需创建实例即可访问。
2.1 静态属性与方法基础
typescript
class MathUtils {
// 静态属性
static readonly PI = 3.14159;
static count = 0;
// 实例属性
result: number = 0;
constructor() {
// 访问静态属性
MathUtils.count++;
}
// 静态方法
static square(num: number): number {
return num * num;
}
// 实例方法可以访问静态成员
calculateCircleArea(radius: number): number {
this.result = MathUtils.PI * MathUtils.square(radius);
return this.result;
}
}
console.log(MathUtils.PI); // 3.14159
console.log(MathUtils.square(4)); // 16
const utils = new MathUtils();
console.log(utils.calculateCircleArea(5)); // 78.53975
console.log(MathUtils.count); // 1
2.2 设计模式中的应用
静态成员在许多设计模式中有重要应用,如工厂模式:
typescript
// 产品接口
interface Product {
name: string;
use(): void;
}
// 具体产品
class ConcreteProductA implements Product {
name = "Product A";
use(): void {
console.log("Using " + this.name);
}
}
class ConcreteProductB implements Product {
name = "Product B";
use(): void {
console.log("Using " + this.name);
}
}
// 工厂类
class ProductFactory {
// 静态工厂方法
static createProduct(type: string): Product {
switch (type) {
case "A":
return new ConcreteProductA();
case "B":
return new ConcreteProductB();
default:
throw new Error(`Product type ${type} not recognized.`);
}
}
}
// 使用工厂
const productA = ProductFactory.createProduct("A");
productA.use(); // "Using Product A"
2.3 单例模式实现
单例模式是静态成员的典型应用,确保一个类只有一个实例:
typescript
class Database {
private static instance: Database | null = null;
private connectionString: string;
// 私有构造函数,防止外部直接实例化
private constructor(connectionString: string) {
this.connectionString = connectionString;
console.log(`Database initialized with: ${connectionString}`);
}
// 获取实例的静态方法
public static getInstance(connectionString: string): Database {
// 如果实例不存在,创建它
if (!Database.instance) {
Database.instance = new Database(connectionString);
}
return Database.instance;
}
public query(sql: string): void {
console.log(`Executing query: ${sql} on ${this.connectionString}`);
}
}
// 使用单例
const db1 = Database.getInstance("mysql://localhost:3306/mydb");
const db2 = Database.getInstance("mysql://localhost:3306/otherdb"); // 不会创建新实例
db1.query("SELECT * FROM users");
db2.query("SELECT * FROM products");
console.log(db1 === db2); // true,两个变量引用同一个实例
更现代的 TypeScript 单例实现:
typescript
class ModernSingleton {
private static instance: ModernSingleton;
// 私有构造函数
private constructor(public readonly id: string = Math.random().toString(36).substr(2, 9)) {
console.log(`ModernSingleton initialized with ID: ${this.id}`);
}
// 使用访问器属性实现单例
public static get Instance(): ModernSingleton {
if (!ModernSingleton.instance) {
ModernSingleton.instance = new ModernSingleton();
}
return ModernSingleton.instance;
}
public doSomething(): void {
console.log(`Singleton ${this.id} doing something...`);
}
}
// 使用
const instance1 = ModernSingleton.Instance;
const instance2 = ModernSingleton.Instance;
instance1.doSomething();
instance2.doSomething();
console.log(instance1.id === instance2.id); // true
3. 类实现接口
接口在 TypeScript 中扮演着核心角色,为类定义契约。
3.1 接口契约与实现
typescript
// 定义接口
interface Vehicle {
make: string;
model: string;
year: number;
start(): void;
stop(): void;
}
// 实现接口
class Car implements Vehicle {
// 必须实现接口中定义的所有属性和方法
make: string;
model: string;
year: number;
// 可以添加接口未定义的其他成员
private isRunning: boolean = false;
constructor(make: string, model: string, year: number) {
this.make = make;
this.model = model;
this.year = year;
}
start(): void {
this.isRunning = true;
console.log(`${this.make} ${this.model} started.`);
}
stop(): void {
this.isRunning = false;
console.log(`${this.make} ${this.model} stopped.`);
}
// 接口未定义的额外方法
honk(): void {
console.log("Beep beep!");
}
}
// 使用实现了接口的类
const myCar: Vehicle = new Car("Toyota", "Corolla", 2022);
myCar.start(); // "Toyota Corolla started."
myCar.stop(); // "Toyota Corolla stopped."
// myCar.honk(); // 错误:Vehicle 接口中没有定义 honk 方法
// 使用完整的 Car 类型可以访问所有方法
const myCar2: Car = new Car("Honda", "Civic", 2023);
myCar2.honk(); // "Beep beep!"
多接口实现策略
TypeScript 允许一个类实现多个接口,这是一种强大的组合机制:
typescript
// 定义多个接口
interface Printable {
print(): void;
}
interface Serializable {
serialize(): string;
}
interface Identifiable {
id: string;
}
interface Loggable {
log(message: string): void;
}
// 实现多个接口
class Document implements Printable, Serializable, Identifiable, Loggable {
id: string;
private content: string;
private logEntries: string[] = [];
constructor(id: string, content: string) {
this.id = id;
this.content = content;
}
// 实现 Printable
print(): void {
console.log(`Printing document: ${this.content}`);
this.log("Document printed");
}
// 实现 Serializable
serialize(): string {
const data = JSON.stringify({
id: this.id,
content: this.content,
timestamp: new Date()
});
this.log("Document serialized");
return data;
}
// 实现 Loggable
log(message: string): void {
const timestamp = new Date().toISOString();
this.logEntries.push(`[${timestamp}] ${message}`);
}
// 获取日志
getLogs(): string[] {
return this.logEntries;
}
}
// 使用多接口实现类
const doc = new Document("doc-123", "TypeScript 接口示例");
// 可以作为任何接口类型使用
const printable: Printable = doc;
printable.print();
const serialized: string = (doc as Serializable).serialize();
console.log(serialized);
// 访问完整功能
console.log(`Document ID: ${doc.id}`);
console.log(`Logs: ${doc.getLogs().join("\n")}`);
3.3 接口组合与扩展
接口可以相互扩展,创建更复杂的类型关系:
typescript
interface Entity {
id: string;
createdAt: Date;
}
interface Named {
name: string;
}
// 扩展多个接口
interface Product extends Entity, Named {
price: number;
description: string;
}
// 实现扩展后的接口
class PhysicalProduct implements Product {
id: string;
createdAt: Date;
name: string;
price: number;
description: string;
weight: number; // 额外属性
constructor(id: string, name: string, price: number, description: string, weight: number) {
this.id = id;
this.createdAt = new Date();
this.name = name;
this.price = price;
this.description = description;
this.weight = weight;
}
// 产品特定方法
calculateShipping(): number {
return this.weight * 0.5;
}
}
const laptop = new PhysicalProduct(
"prod-001",
"Laptop Pro",
1299,
"High-performance laptop",
2.5
);
console.log(`Shipping cost: $${laptop.calculateShipping()}`);
4. 装饰器模式
装饰器是 TypeScript 和 ES 规范中的一项实验性特性,允许以声明方式修改类、方法、属性和参数。
注意 :要使用装饰器,需要在
tsconfig.json
中启用experimentalDecorators
选项。
4.1 类装饰器
类装饰器应用于类的构造函数,可以用来观察、修改或替换类定义:
typescript
// 简单的类装饰器
function sealed(target: Function) {
Object.seal(target);
Object.seal(target.prototype);
console.log(`${target.name} has been sealed`);
}
// 带参数的装饰器工厂
function component(name: string) {
// 返回实际的装饰器函数
return function(target: Function) {
console.log(`Registering component: ${name}`);
// 为类添加元数据
Reflect.defineMetadata('componentName', name, target);
};
}
// 应用装饰器
@sealed
@component('user-profile')
class UserProfile {
constructor(private username: string) {}
getProfile() {
return `User: ${this.username}`;
}
}
// 检索元数据(需要引入 reflect-metadata)
console.log(Reflect.getMetadata('componentName', UserProfile)); // 'user-profile'
4.2 方法与属性装饰器
typescript
// 方法装饰器
function log(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
// 保存原始方法
const originalMethod = descriptor.value;
// 替换方法
descriptor.value = function(...args: any[]) {
console.log(`Calling ${propertyKey} with:`, args);
// 调用原始方法并返回结果
const result = originalMethod.apply(this, args);
console.log(`Result of ${propertyKey}:`, result);
return result;
};
return descriptor;
}
// 属性装饰器
function format(formatString: string) {
return function(target: any, propertyKey: string) {
// 属性值
let value: any;
// 创建 getter
const getter = function() {
return value;
};
// 创建 setter
const setter = function(newValue: any) {
if (typeof newValue === 'number' && formatString === 'currency') {
value = `$${newValue.toFixed(2)}`;
} else {
value = newValue;
}
};
// 替换属性
Object.defineProperty(target, propertyKey, {
get: getter,
set: setter,
enumerable: true,
configurable: true
});
};
}
class Order {
@format('currency')
price: number;
constructor(price: number) {
this.price = price;
}
@log
calculateTotal(tax: number): number {
const numericPrice = parseFloat(String(this.price).replace('$', ''));
return numericPrice * (1 + tax);
}
}
const order = new Order(29.99);
console.log(order.price); // "$29.99"
const total = order.calculateTotal(0.07); // 日志输出并返回总价
4.3 装饰器工厂函数
装饰器工厂是返回装饰器的函数,允许自定义装饰器行为:
typescript
// 参数装饰器工厂
function required(target: Object, propertyKey: string, parameterIndex: number) {
// 获取方法原始参数的元数据(需要 reflect-metadata)
const existingRequiredParameters: number[] = Reflect.getOwnMetadata(
'required',
target,
propertyKey
) || [];
// 添加当前参数索引
existingRequiredParameters.push(parameterIndex);
// 更新元数据
Reflect.defineMetadata(
'required',
existingRequiredParameters,
target,
propertyKey
);
}
// 方法装饰器工厂,验证必填参数
function validate(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = function(...args: any[]) {
// 获取必填参数列表
const requiredParameters: number[] = Reflect.getOwnMetadata(
'required',
target,
propertyKey
) || [];
// 验证参数
for (const index of requiredParameters) {
if (index >= args.length || args[index] === undefined || args[index] === null) {
throw new Error(`Parameter at index ${index} is required.`);
}
}
return originalMethod.apply(this, args);
};
return descriptor;
}
// 更复杂的装饰器组合
class API {
@validate
makeRequest(
@required url: string,
method: string = 'GET',
@required data: object,
headers?: object
) {
console.log(`Making ${method} request to ${url} with data:`, data);
// 实际请求逻辑...
return { success: true };
}
}
const api = new API();
// 正常工作
api.makeRequest('https://api.example.com', 'POST', { id: 123 });
// 抛出错误:url 为必填
// api.makeRequest(undefined, 'GET', { id: 123 });
// 抛出错误:data 为必填
// api.makeRequest('https://api.example.com', 'GET', undefined);
总结
随着 TypeScript 和 ECMAScript 标准的发展,面向对象特性将继续完善,为开发者提供更强大的工具来构建复杂的应用程序。使用这些特性时,关键是保持代码的简洁性和可读性,避免过度设计。