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。
- 抽象类:不能实例化的类,用于定义基类和强制子类实现特定方法。
面向对象编程最佳实践
- 单一职责原则:一个类应该只负责一项功能,提高代码的可维护性。
- 开放/封闭原则:对扩展开放,对修改封闭,通过继承和接口实现扩展。
- 里氏替换原则:子类应该能够替换父类并保持行为一致性。
- 依赖倒置原则:依赖于抽象而非具体实现,使用接口和抽象类降低耦合。
- 接口隔离原则:使用多个专门的接口而非一个统一的接口,避免实现不需要的方法。
- 使用访问修饰符:合理使用public、private和protected,隐藏内部实现细节。
- 优先使用接口:接口提供更好的灵活性和多继承支持,适合类型定义。
- 明确标记重写方法:使用
override关键字显式标记重写的方法,提高代码可读性。 - 避免深层继承:继承层次过深会降低代码可读性,考虑组合优于继承。
- 使用TypeScript高级类型:如泛型、映射类型等增强类型安全性和代码复用。
TypeScript面向对象编程的优势
- 类型安全:静态类型检查在编译时捕获错误,减少运行时异常。
- 代码可读性:明确的类型定义和接口使代码更易于理解和维护。
- IDE支持:更好的自动完成、重构和导航功能,提高开发效率。
- 更好的JavaScript互操作性:可以渐进式采用,与现有JavaScript代码无缝集成。
- 面向对象特性增强:提供比原生JavaScript更完善的类、接口和继承支持。
通过掌握TypeScript的面向对象编程特性,开发者可以构建更健壮、可维护和类型安全的应用程序,尤其适合中大型项目开发和团队协作。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



