TypeScript系列03-类与面向对象编程

面向对象编程(OOP)是 TypeScript 的核心特性之一,它使得代码组织更加结构化且可维护。本文将深入探讨 TypeScript 中类的各个方面,从基础概念到高级应用,帮助你全面理解并有效运用面向对象的设计思想。本文涵盖:

  1. 访问修饰符 (public、private、protected)和抽象类提供了代码封装和继承的机制
  2. 静态成员支持类级别的属性和方法,在单例、工厂等设计模式中发挥重要作用
  3. 接口实现增强了代码的可重用性和类型安全,通过契约定义类必须符合的结构
  4. 装饰器提供了强大的元编程能力,用于以非侵入式方式扩展类和成员的功能

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 标准的发展,面向对象特性将继续完善,为开发者提供更强大的工具来构建复杂的应用程序。使用这些特性时,关键是保持代码的简洁性和可读性,避免过度设计。

相关推荐
IT、木易6 小时前
大白话TypeScript 第十章TypeScript 学习全阶段详细总结
前端·学习·typescript
浩男孩8 小时前
面试官提问:TypeScript 中的 Type 和 Interface 有什么区别?
前端·typescript
i建模2 天前
Electron+Vite+React+TypeScript开发问题手册
react.js·typescript·electron
i建模2 天前
Electron + Vite + React + TypeScript 跨平台开发实践指南
react.js·typescript·electron·vite
没资格抱怨3 天前
TypeScript中的接口(interface)
前端·javascript·typescript
m0_748252233 天前
TypeScript 与后端开发Node.js
javascript·typescript·node.js
IT、木易4 天前
大白话TypeScript第八章TypeScript 项目的部署与监控
前端·javascript·typescript
鹤鸣的日常4 天前
vue3 封装通用 ECharts 组件
前端·javascript·vue.js·typescript·echarts
Min_nna4 天前
web前端初学Angular由浅入深上手开发项目
前端·typescript·angular.js