TypeScript面向对象编程:类、接口与继承的完整指南

TypeScript面向对象编程:类、接口与继承的完整指南

【免费下载链接】TypeScript microsoft/TypeScript: 是 TypeScript 的官方仓库,包括 TypeScript 语的定义和编译器。适合对 TypeScript、JavaScript 和想要使用 TypeScript 进行类型检查的开发者。 【免费下载链接】TypeScript 项目地址: https://gitcode.com/GitHub_Trending/ty/TypeScript

引言:TypeScript如何重塑JavaScript面向对象范式

JavaScript作为一门基于原型(Prototype)的语言,其面向对象编程(Object-Oriented Programming, OOP)实现方式与传统基于类(Class)的语言存在显著差异。TypeScript通过引入类(Class)、接口(Interface)、泛型(Generics)等特性,在保持JavaScript动态性的同时,提供了更严格的类型检查和更接近传统OOP的编程体验。本文将系统讲解TypeScript面向对象编程的核心概念与实践技巧,帮助开发者构建类型安全、结构清晰的应用程序。

类(Class):TypeScript面向对象的基石

类的定义与基本结构

在TypeScript中,类是创建对象的模板,包含属性(Properties)和方法(Methods)。通过class关键字定义类,使用constructor方法初始化对象。

class Person {
  // 属性定义
  name: string;
  age: number;

  // 构造函数
  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }

  // 方法定义
  greet(): string {
    return `Hello, my name is ${this.name} and I'm ${this.age} years old.`;
  }
}

// 创建类实例
const person = new Person("Alice", 30);
console.log(person.greet()); // 输出: Hello, my name is Alice and I'm 30 years old.

访问修饰符:控制成员的可访问性

TypeScript提供三种访问修饰符(Access Modifiers),用于控制类成员的可访问范围:

  • public:默认修饰符,成员可在任何地方访问
  • private:成员仅可在类内部访问
  • protected:成员可在类内部和子类中访问
class Animal {
  public name: string;    // 公开属性
  private age: number;    // 私有属性
  protected species: string; // 受保护属性

  constructor(name: string, age: number, species: string) {
    this.name = name;
    this.age = age;
    this.species = species;
  }

  public getAge(): number {
    return this.age; // 类内部可访问私有属性
  }

  protected getSpecies(): string {
    return this.species; // 受保护方法
  }
}

class Dog extends Animal {
  constructor(name: string, age: number) {
    super(name, age, "Canine");
  }

  public getDogInfo(): string {
    // 子类可访问protected成员
    return `${this.name} is a ${this.getSpecies()} aged ${this.getAge()}`;
  }
}

const dog = new Dog("Buddy", 5);
console.log(dog.name); // 可访问public成员
console.log(dog.getAge()); // 通过public方法访问private成员
console.log(dog.getDogInfo()); // 子类方法访问protected成员
// console.log(dog.age); // 错误:private成员不可在外部访问
// console.log(dog.species); // 错误:protected成员不可在外部访问

只读属性(Readonly)

使用readonly关键字声明只读属性,只能在声明时或构造函数中初始化,之后不可修改。

class Circle {
  readonly radius: number;
  readonly pi: number = 3.14159; // 声明时初始化

  constructor(radius: number) {
    this.radius = radius; // 构造函数中初始化
  }

  getArea(): number {
    return this.pi * this.radius ** 2;
  }
}

const circle = new Circle(5);
console.log(circle.radius); // 输出: 5
// circle.radius = 10; // 错误:只读属性不可修改

静态成员(Static Members)

静态成员属于类本身而非实例,使用static关键字声明,通过类名直接访问。

class MathUtils {
  static readonly PI: number = 3.14159;

  static calculateCircumference(radius: number): number {
    return 2 * this.PI * radius;
  }

  static calculateArea(radius: number): number {
    return this.PI * radius ** 2;
  }
}

console.log(MathUtils.PI); // 输出: 3.14159
console.log(MathUtils.calculateArea(5)); // 输出: 78.53975

抽象类(Abstract Classes)

抽象类是不能实例化的类,用于定义其他类的共同接口。使用abstract关键字声明,抽象方法必须在派生类中实现。

abstract class Shape {
  abstract getArea(): number; // 抽象方法,无实现
  abstract getPerimeter(): number;

  // 具体方法
  getDimensions(): string {
    return "This shape has unknown dimensions";
  }
}

class Rectangle extends Shape {
  constructor(private width: number, private height: number) {
    super();
  }

  // 实现抽象方法
  getArea(): number {
    return this.width * this.height;
  }

  getPerimeter(): number {
    return 2 * (this.width + this.height);
  }

  // 重写具体方法
  getDimensions(): string {
    return `Width: ${this.width}, Height: ${this.height}`;
  }
}

// const shape = new Shape(); // 错误:抽象类不能实例化
const rectangle = new Rectangle(5, 10);
console.log(rectangle.getArea()); // 输出: 50
console.log(rectangle.getDimensions()); // 输出: Width: 5, Height: 10

接口(Interface):定义契约与类型

接口的基本定义与使用

接口(Interface)定义对象的结构和类型,指定对象必须包含的属性和方法,但不提供实现。接口主要用于类型检查和定义契约。

// 定义接口
interface User {
  id: number;
  name: string;
  email: string;
  age?: number; // 可选属性
  readonly createdAt: Date; // 只读属性
}

// 使用接口作为类型注解
function createUser(user: User): void {
  console.log(`Creating user: ${user.name}, Email: ${user.email}`);
}

// 符合接口结构的对象
const newUser: User = {
  id: 1,
  name: "John Doe",
  email: "john@example.com",
  createdAt: new Date()
};

createUser(newUser); // 正确

// 缺少属性会报错
const invalidUser = {
  id: 2,
  name: "Jane Doe"
  // 缺少email属性
};
// createUser(invalidUser); // 错误:缺少email属性

函数类型接口

接口可定义函数类型,指定函数的参数类型和返回类型。

// 定义函数类型接口
interface MathOperation {
  (a: number, b: number): number;
}

// 实现接口
const add: MathOperation = (x, y) => x + y;
const multiply: MathOperation = (x, y) => x * y;

console.log(add(5, 3)); // 输出: 8
console.log(multiply(5, 3)); // 输出: 15

类实现接口(Implements)

类可以使用implements关键字实现接口,必须实现接口中定义的所有成员。

interface Vehicle {
  speed: number;
  accelerate(amount: number): void;
  brake(amount: number): void;
}

class Car implements Vehicle {
  speed: number = 0;

  accelerate(amount: number): void {
    this.speed += amount;
  }

  brake(amount: number): void {
    this.speed = Math.max(0, this.speed - amount);
  }

  // 类可以有接口之外的方法
  honk(): void {
    console.log("Honking!");
  }
}

const car = new Car();
car.accelerate(50);
console.log(car.speed); // 输出: 50
car.brake(20);
console.log(car.speed); // 输出: 30
car.honk(); // 输出: Honking!

接口继承(Extends)

接口可以继承其他接口,实现接口的组合与扩展。

interface Shape {
  color: string;
  getArea(): number;
}

// 继承接口
interface Circle extends Shape {
  radius: number;
}

interface Rectangle extends Shape {
  width: number;
  height: number;
}

// 实现继承的接口
const circle: Circle = {
  color: "red",
  radius: 5,
  getArea() {
    return Math.PI * this.radius ** 2;
  }
};

const rectangle: Rectangle = {
  color: "blue",
  width: 10,
  height: 5,
  getArea() {
    return this.width * this.height;
  }
};

console.log(circle.getArea()); // 输出: 78.5398...
console.log(rectangle.getArea()); // 输出: 50

接口合并(Interface Merging)

TypeScript允许同名接口自动合并,合并后的接口包含所有接口的成员。

// 第一个接口定义
interface Person {
  name: string;
  age: number;
}

// 同名接口会合并
interface Person {
  email: string;
  greet(): string;
}

// 合并后的接口包含所有成员
const person: Person = {
  name: "Alice",
  age: 30,
  email: "alice@example.com",
  greet() {
    return `Hello, I'm ${this.name}`;
  }
};

console.log(person.greet()); // 输出: Hello, I'm Alice

继承(Inheritance):代码复用与扩展

类的继承

TypeScript支持类的单继承,使用extends关键字创建子类,继承父类的属性和方法。子类可通过super关键字调用父类的构造函数和方法。

class Animal {
  name: string;
  age: number;

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

  makeSound(): void {
    console.log("Some generic animal sound");
  }

  getInfo(): string {
    return `${this.name} is ${this.age} years old`;
  }
}

// 继承Animal类
class Cat extends Animal {
  breed: string;

  // 子类构造函数
  constructor(name: string, age: number, breed: string) {
    super(name, age); // 调用父类构造函数
    this.breed = breed;
  }

  // 重写父类方法
  makeSound(): void {
    console.log("Meow!");
  }

  // 子类新增方法
  purr(): void {
    console.log("Purring...");
  }

  // 扩展父类方法
  getInfo(): string {
    // 调用父类方法
    return `${super.getInfo()}, Breed: ${this.breed}`;
  }
}

const cat = new Cat("Whiskers", 3, "Siamese");
console.log(cat.getInfo()); // 输出: Whiskers is 3 years old, Breed: Siamese
cat.makeSound(); // 输出: Meow!
cat.purr(); // 输出: Purring...

方法重写(Method Overriding)

子类可以重写父类的方法,提供新的实现。TypeScript建议使用override关键字显式标记重写的方法,增强代码可读性和安全性。

class Employee {
  name: string;
  position: string;

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

  getSalary(): number {
    return 50000; // 默认薪资
  }
}

class Developer extends Employee {
  programmingLanguages: string[];

  constructor(name: string, position: string, languages: string[]) {
    super(name, position);
    this.programmingLanguages = languages;
  }

  // 重写父类方法,使用override关键字
  override getSalary(): number {
    // 基础薪资 + 语言技能奖金
    return 70000 + (this.programmingLanguages.length * 5000);
  }

  getLanguages(): string {
    return this.programmingLanguages.join(", ");
  }
}

const dev = new Developer("Bob", "Senior Developer", ["TypeScript", "JavaScript", "Python"]);
console.log(dev.getSalary()); // 输出: 85000 (70000 + 3*5000)
console.log(dev.getLanguages()); // 输出: TypeScript, JavaScript, Python

多态(Polymorphism)

多态是面向对象的重要特性,允许不同类型的对象对同一方法做出不同响应。

class Shape {
  name: string;

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

  getArea(): number {
    return 0; // 基类默认实现
  }

  describe(): string {
    return `This is a ${this.name}`;
  }
}

class Square extends Shape {
  sideLength: number;

  constructor(sideLength: number) {
    super("Square");
    this.sideLength = sideLength;
  }

  override getArea(): number {
    return this.sideLength ** 2;
  }
}

class Triangle extends Shape {
  base: number;
  height: number;

  constructor(base: number, height: number) {
    super("Triangle");
    this.base = base;
    this.height = height;
  }

  override getArea(): number {
    return (this.base * this.height) / 2;
  }
}

// 多态数组:可包含不同子类实例
const shapes: Shape[] = [
  new Square(5),
  new Triangle(4, 6),
  new Square(3),
  new Triangle(5, 10)
];

// 多态调用:同一方法不同实现
shapes.forEach(shape => {
  console.log(`${shape.describe()}, Area: ${shape.getArea()}`);
});
// 输出:
// This is a Square, Area: 25
// This is a Triangle, Area: 12
// This is a Square, Area: 9
// This is a Triangle, Area: 25

继承与接口结合

类可以同时继承父类和实现接口,实现代码复用和接口约束。

// 定义接口
interface Printable {
  print(): void;
}

interface Scannable {
  scan(): void;
}

// 基础类
class Device {
  brand: string;
  model: string;

  constructor(brand: string, model: string) {
    this.brand = brand;
    this.model = model;
  }

  getInfo(): string {
    return `${this.brand} ${this.model}`;
  }
}

// 继承Device类并实现多个接口
class PrinterScanner extends Device implements Printable, Scannable {
  constructor(brand: string, model: string) {
    super(brand, model);
  }

  // 实现Printable接口
  print(): void {
    console.log(`${this.getInfo()} is printing...`);
  }

  // 实现Scannable接口
  scan(): void {
    console.log(`${this.getInfo()} is scanning...`);
  }

  // 新增方法
  copy(): void {
    console.log(`${this.getInfo()} is copying...`);
  }
}

const officeMachine = new PrinterScanner("HP", "OfficeJet Pro");
officeMachine.print(); // 输出: HP OfficeJet Pro is printing...
officeMachine.scan(); // 输出: HP OfficeJet Pro is scanning...
officeMachine.copy(); // 输出: HP OfficeJet Pro is copying...

高级特性:抽象类与接口的深入应用

抽象类与接口的区别

抽象类和接口都用于定义契约和实现多态,但有重要区别:

特性抽象类接口
实现可包含实现代码不能包含实现代码(TypeScript 2.0+可包含默认方法)
继承单继承多实现
构造函数可以有构造函数不能有构造函数
成员可包含实例成员和静态成员只能定义实例成员(不能有静态成员)
访问修饰符支持public、private、protected所有成员默认为public
用途代码复用和类型约束主要用于类型约束和接口定义

何时使用抽象类 vs 接口

  • 使用抽象类:当需要共享实现代码、定义构造函数、使用访问修饰符控制成员访问时。
  • 使用接口:当需要定义对象结构、实现多继承、类型检查或定义回调函数类型时。
// 抽象类:提供基础实现
abstract class DataService {
  protected data: any[] = [];

  abstract fetchData(): Promise<void>;

  saveData(item: any): void {
    this.data.push(item);
    console.log("Data saved:", item);
  }

  getData(): any[] {
    return [...this.data]; // 返回数据副本
  }
}

// 接口:定义数据验证契约
interface Validatable {
  validate(data: any): boolean;
}

// 继承抽象类并实现接口
class UserService extends DataService implements Validatable {
  override async fetchData(): Promise<void> {
    // 模拟API调用
    this.data = [
      { id: 1, name: "John", email: "john@example.com" },
      { id: 2, name: "Jane", email: "jane@example.com" }
    ];
  }

  // 实现Validatable接口
  validate(user: any): boolean {
    return !!user.name && user.email?.includes("@");
  }

  // 新增方法
  getUserById(id: number): any {
    return this.data.find(user => user.id === id);
  }
}

// 使用服务
async function useUserService() {
  const userService = new UserService();
  await userService.fetchData();
  
  console.log("All users:", userService.getData());
  
  const newUser = { id: 3, name: "Bob", email: "bob@example.com" };
  if (userService.validate(newUser)) {
    userService.saveData(newUser);
  }
  
  console.log("User with ID 2:", userService.getUserById(2));
}

useUserService();

接口的高级应用:索引类型与映射类型

TypeScript接口支持索引类型和映射类型,提供更灵活的类型定义。

索引类型

定义可通过索引访问的对象类型:

// 字符串索引类型
interface StringMap {
  [key: string]: string;
}

const dictionary: StringMap = {
  hello: "world",
  typescript: "language"
};

console.log(dictionary["hello"]); // 输出: world

// 数字索引类型
interface NumberArray {
  [index: number]: number;
  length: number; // 允许定义其他属性
}

const numbers: NumberArray = [1, 2, 3, 4];
console.log(numbers[2]); // 输出: 3
console.log(numbers.length); // 输出: 4
映射类型

基于现有类型创建新类型,通常与泛型结合使用:

// 定义接口
interface Product {
  id: number;
  name: string;
  price: number;
}

// 映射类型:将所有属性变为可选
type PartialProduct = {
  [P in keyof Product]?: Product[P];
};

// 映射类型:将所有属性变为只读
type ReadonlyProduct = {
  readonly [P in keyof Product]: Product[P];
};

// 使用映射类型
const partialProduct: PartialProduct = {
  name: "New Product"
  // 其他属性可选
};

const readonlyProduct: ReadonlyProduct = {
  id: 1,
  name: "Laptop",
  price: 999
};

// readonlyProduct.price = 899; // 错误:只读属性不可修改

实践案例:构建TypeScript面向对象应用

案例:电商产品系统

以下是一个电商产品系统的实现,综合运用类、接口、继承等面向对象特性:

// 定义接口
interface Discountable {
  calculateDiscount(): number;
}

interface Taxable {
  calculateTax(): number;
}

// 抽象基类
abstract class Product {
  protected id: number;
  protected name: string;
  protected price: number;
  protected createdAt: Date;

  constructor(id: number, name: string, price: number) {
    this.id = id;
    this.name = name;
    this.price = price;
    this.createdAt = new Date();
  }

  abstract getCategory(): string;

  getDetails(): string {
    return `ID: ${this.id}, Name: ${this.name}, Price: $${this.price.toFixed(2)}, Category: ${this.getCategory()}`;
  }

  getPrice(): number {
    return this.price;
  }
}

// 具体产品类:电子设备
class ElectronicProduct extends Product implements Discountable, Taxable {
  private warrantyPeriod: number; // 保修期限(月)
  private brand: string;

  constructor(id: number, name: string, price: number, brand: string, warrantyPeriod: number) {
    super(id, name, price);
    this.brand = brand;
    this.warrantyPeriod = warrantyPeriod;
  }

  override getCategory(): string {
    return "Electronics";
  }

  // 实现Discountable接口
  calculateDiscount(): number {
    // 电子产品9折优惠
    return this.price * 0.1;
  }

  // 实现Taxable接口
  calculateTax(): number {
    // 电子类产品税率10%
    return this.price * 0.1;
  }

  getWarrantyInfo(): string {
    return `${this.warrantyPeriod} months warranty`;
  }
}

// 具体产品类:服装
class ClothingProduct extends Product implements Discountable {
  private size: string;
  private color: string;

  constructor(id: number, name: string, price: number, size: string, color: string) {
    super(id, name, price);
    this.size = size;
    this.color = color;
  }

  override getCategory(): string {
    return "Clothing";
  }

  // 实现Discountable接口
  calculateDiscount(): number {
    // 服装类85折优惠
    return this.price * 0.15;
  }

  getClothingInfo(): string {
    return `Size: ${this.size}, Color: ${this.color}`;
  }
}

// 购物车类
class ShoppingCart {
  private items: { product: Product, quantity: number }[] = [];

  addItem(product: Product, quantity: number = 1): void {
    if (quantity <= 0) {
      throw new Error("Quantity must be positive");
    }
    
    this.items.push({ product, quantity });
    console.log(`Added ${quantity} x ${product.getDetails()}`);
  }

  removeItem(productId: number): void {
    this.items = this.items.filter(item => item.product["id"] !== productId);
  }

  calculateTotal(): number {
    let total = 0;
    
    for (const { product, quantity } of this.items) {
      let price = product.getPrice();
      
      // 应用折扣
      if ("calculateDiscount" in product) {
        price -= (product as Discountable).calculateDiscount();
      }
      
      // 应用税费
      if ("calculateTax" in product) {
        price += (product as Taxable).calculateTax();
      }
      
      total += price * quantity;
    }
    
    return parseFloat(total.toFixed(2));
  }

  getCartItems(): string {
    return this.items.map(item => {
      return `${item.quantity} x ${item.product.getDetails()}`;
    }).join("\n");
  }
}

// 使用系统
function runShoppingSystem() {
  // 创建产品
  const laptop = new ElectronicProduct(1, "Laptop", 999.99, "Dell", 24);
  const shirt = new ClothingProduct(2, "T-Shirt", 29.99, "M", "Blue");
  const phone = new ElectronicProduct(3, "Smartphone", 699.99, "Apple", 12);

  // 创建购物车
  const cart = new ShoppingCart();
  
  // 添加商品
  cart.addItem(laptop, 1);
  cart.addItem(shirt, 2);
  cart.addItem(phone, 1);

  // 显示购物车
  console.log("Shopping Cart Items:");
  console.log(cart.getCartItems());
  
  // 计算总价
  console.log(`Total Price: $${cart.calculateTotal().toFixed(2)}`);
  
  // 显示产品特定信息
  console.log("\nProduct Specific Information:");
  console.log(`${laptop.name} Warranty: ${laptop.getWarrantyInfo()}`);
  console.log(`${shirt.name} Details: ${shirt.getClothingInfo()}`);
}

runShoppingSystem();

总结与最佳实践

核心概念回顾

  • 类(Class):定义对象的属性和方法,是创建对象的模板。
  • 接口(Interface):定义对象的结构和类型,用于类型检查和契约定义。
  • 继承(Inheritance):子类继承父类的属性和方法,实现代码复用和扩展。
  • 访问修饰符:控制类成员的可访问性,包括public、private和protected。
  • 抽象类:不能实例化的类,用于定义基类和强制子类实现特定方法。

面向对象编程最佳实践

  1. 单一职责原则:一个类应该只负责一项功能,提高代码的可维护性。
  2. 开放/封闭原则:对扩展开放,对修改封闭,通过继承和接口实现扩展。
  3. 里氏替换原则:子类应该能够替换父类并保持行为一致性。
  4. 依赖倒置原则:依赖于抽象而非具体实现,使用接口和抽象类降低耦合。
  5. 接口隔离原则:使用多个专门的接口而非一个统一的接口,避免实现不需要的方法。
  6. 使用访问修饰符:合理使用public、private和protected,隐藏内部实现细节。
  7. 优先使用接口:接口提供更好的灵活性和多继承支持,适合类型定义。
  8. 明确标记重写方法:使用override关键字显式标记重写的方法,提高代码可读性。
  9. 避免深层继承:继承层次过深会降低代码可读性,考虑组合优于继承。
  10. 使用TypeScript高级类型:如泛型、映射类型等增强类型安全性和代码复用。

TypeScript面向对象编程的优势

  • 类型安全:静态类型检查在编译时捕获错误,减少运行时异常。
  • 代码可读性:明确的类型定义和接口使代码更易于理解和维护。
  • IDE支持:更好的自动完成、重构和导航功能,提高开发效率。
  • 更好的JavaScript互操作性:可以渐进式采用,与现有JavaScript代码无缝集成。
  • 面向对象特性增强:提供比原生JavaScript更完善的类、接口和继承支持。

通过掌握TypeScript的面向对象编程特性,开发者可以构建更健壮、可维护和类型安全的应用程序,尤其适合中大型项目开发和团队协作。

【免费下载链接】TypeScript microsoft/TypeScript: 是 TypeScript 的官方仓库,包括 TypeScript 语的定义和编译器。适合对 TypeScript、JavaScript 和想要使用 TypeScript 进行类型检查的开发者。 【免费下载链接】TypeScript 项目地址: https://gitcode.com/GitHub_Trending/ty/TypeScript

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值