TypeScript类和继承的实现方式
TypeScript 类的基础定义与使用
在 TypeScript 中,类是面向对象编程的核心概念之一。类就像是一个蓝图,用于创建对象。我们通过定义类来描述对象的属性和行为。
首先,来看一个简单的类定义示例:
class Person {
name: string;
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
greet() {
return `Hello, my name is ${this.name} and I'm ${this.age} years old.`;
}
}
在上述代码中:
- 属性定义:我们定义了两个属性
name
和age
,分别为字符串类型和数字类型。这些属性用于存储对象的相关数据。 - 构造函数:
constructor
是类的特殊方法,用于在创建对象时初始化对象的属性。这里接受两个参数name
和age
,并将它们赋值给对应的对象属性。 - 方法定义:
greet
方法是一个实例方法,它返回一个包含对象name
和age
的问候字符串。
要使用这个类,我们可以这样创建对象:
let person1 = new Person('Alice', 30);
console.log(person1.greet());
类的访问修饰符
TypeScript 提供了几种访问修饰符,用于控制类的属性和方法的访问权限。
- public(公共的):这是默认的访问修饰符。被
public
修饰的属性和方法可以在类的内部和外部被访问。
class Animal {
public name: string;
constructor(name: string) {
this.name = name;
}
public makeSound() {
return `${this.name} makes a sound.`;
}
}
let dog = new Animal('Buddy');
console.log(dog.name);
console.log(dog.makeSound());
- private(私有的):被
private
修饰的属性和方法只能在类的内部被访问。
class SecretData {
private data: string;
constructor(data: string) {
this.data = data;
}
private printData() {
console.log(this.data);
}
public showData() {
this.printData();
}
}
let secret = new SecretData('Confidential information');
// console.log(secret.data); // 这将导致编译错误,因为 data 是私有的
// secret.printData(); // 这也将导致编译错误,因为 printData 是私有的
secret.showData();
- protected(受保护的):被
protected
修饰的属性和方法可以在类的内部以及它的子类中被访问,但不能在类的外部被访问。
class Shape {
protected color: string;
constructor(color: string) {
this.color = color;
}
protected getColor() {
return this.color;
}
}
class Circle extends Shape {
radius: number;
constructor(color: string, radius: number) {
super(color);
this.radius = radius;
}
public describe() {
return `This is a ${this.getColor()} circle with radius ${this.radius}.`;
}
}
let circle = new Circle('red', 5);
// console.log(circle.color); // 这将导致编译错误,因为 color 是受保护的
// console.log(circle.getColor()); // 这也将导致编译错误,因为 getColor 是受保护的
console.log(circle.describe());
类的静态成员
静态成员是属于类本身而不是类的实例的属性和方法。我们使用 static
关键字来定义静态成员。
class MathUtils {
static PI: number = 3.14159;
static calculateCircleArea(radius: number) {
return this.PI * radius * radius;
}
}
console.log(MathUtils.PI);
console.log(MathUtils.calculateCircleArea(5));
在上述代码中,PI
是一个静态属性,calculateCircleArea
是一个静态方法。我们通过类名直接访问静态成员,而不需要创建类的实例。
TypeScript 中的继承
继承是面向对象编程的重要特性之一,它允许我们创建一个新类(子类),从一个已有的类(父类)中获取属性和方法。在 TypeScript 中,使用 extends
关键字来实现继承。
class Vehicle {
brand: string;
model: string;
constructor(brand: string, model: string) {
this.brand = brand;
this.model = model;
}
startEngine() {
return `The ${this.brand} ${this.model}'s engine has started.`;
}
}
class Car extends Vehicle {
numDoors: number;
constructor(brand: string, model: string, numDoors: number) {
super(brand, model);
this.numDoors = numDoors;
}
showDetails() {
return `This ${this.brand} ${this.model} has ${this.numDoors} doors.`;
}
}
let myCar = new Car('Toyota', 'Corolla', 4);
console.log(myCar.startEngine());
console.log(myCar.showDetails());
在这个例子中:
- 定义父类:
Vehicle
类是父类,它有brand
和model
两个属性,以及startEngine
方法。 - 定义子类:
Car
类通过extends
关键字继承自Vehicle
类。它不仅拥有Vehicle
类的属性和方法,还定义了自己特有的numDoors
属性和showDetails
方法。 - 调用父类构造函数:在
Car
类的构造函数中,我们使用super
关键字调用父类的构造函数,以初始化从父类继承的属性。
重写父类方法
子类可以重写父类中定义的方法,以提供更具体或不同的实现。
class Animal {
name: string;
constructor(name: string) {
this.name = name;
}
makeSound() {
return `${this.name} makes a generic sound.`;
}
}
class Dog extends Animal {
breed: string;
constructor(name: string, breed: string) {
super(name);
this.breed = breed;
}
makeSound() {
return `${this.name} (a ${this.breed}) barks.`;
}
}
let myDog = new Dog('Buddy', 'Golden Retriever');
console.log(myDog.makeSound());
在上述代码中,Dog
类重写了 Animal
类的 makeSound
方法,提供了更具体的狗叫的实现。
抽象类和抽象方法
抽象类是一种不能被实例化的类,它主要用于作为其他类的基类。抽象类可以包含抽象方法,抽象方法是只有声明而没有实现的方法,子类必须重写这些抽象方法。
abstract class Shape {
abstract calculateArea(): number;
}
class Rectangle extends Shape {
width: number;
height: number;
constructor(width: number, height: number) {
super();
this.width = width;
this.height = height;
}
calculateArea() {
return this.width * this.height;
}
}
class Circle extends Shape {
radius: number;
constructor(radius: number) {
super();
this.radius = radius;
}
calculateArea() {
return Math.PI * this.radius * this.radius;
}
}
let rectangle = new Rectangle(5, 10);
let circle = new Circle(7);
console.log(rectangle.calculateArea());
console.log(circle.calculateArea());
在上述代码中:
- 抽象类定义:
Shape
是一个抽象类,它包含一个抽象方法calculateArea
。 - 子类实现:
Rectangle
和Circle
类继承自Shape
类,并分别实现了calculateArea
方法,以计算各自的面积。
多重继承的替代方案 - 混入(Mixins)
在 TypeScript 中,不像一些其他语言那样直接支持多重继承,但可以通过混入(Mixins)模式来实现类似的功能。混入是一种将多个类的功能合并到一个类中的技术。
// 定义混入类
class Logger {
log(message: string) {
console.log(`[LOG] ${message}`);
}
}
class Timestamp {
getTimestamp() {
return new Date().toISOString();
}
}
// 定义混入函数
function applyMixins(derivedCtor: any, baseCtors: any[]) {
baseCtors.forEach(baseCtor => {
Object.getOwnPropertyNames(baseCtor.prototype).forEach(name => {
if (name!== 'constructor') {
derivedCtor.prototype[name] = baseCtor.prototype[name];
}
});
});
}
// 使用混入
class MyClass {
data: string;
constructor(data: string) {
this.data = data;
}
}
applyMixins(MyClass, [Logger, Timestamp]);
let myObj = new MyClass('Some data');
myObj.log(`Data at ${myObj.getTimestamp()}: ${myObj.data}`);
在上述代码中:
- 定义混入类:
Logger
类提供日志记录功能,Timestamp
类提供获取时间戳功能。 - 定义混入函数:
applyMixins
函数将多个混入类的方法合并到目标类的原型上。 - 使用混入:
MyClass
通过applyMixins
函数混入了Logger
和Timestamp
的功能,从而可以使用它们的方法。
类与接口的关系
在 TypeScript 中,类和接口紧密相关。接口可以用于描述类的形状,即类应该具有哪些属性和方法。
- 类实现接口:一个类可以实现一个或多个接口,以保证满足接口定义的契约。
interface Printable {
print(): void;
}
class Book implements Printable {
title: string;
constructor(title: string) {
this.title = title;
}
print() {
console.log(`Book title: ${this.title}`);
}
}
let book = new Book('TypeScript in Action');
book.print();
在上述代码中,Book
类实现了 Printable
接口,必须提供 print
方法的实现。
- 接口继承接口:接口可以继承其他接口,形成更复杂的接口结构。
interface Shape {
area(): number;
}
interface Colorable {
color: string;
}
interface ColoredShape extends Shape, Colorable {
// 这里可以添加更多属性或方法
}
class Square implements ColoredShape {
sideLength: number;
color: string;
constructor(sideLength: number, color: string) {
this.sideLength = sideLength;
this.color = color;
}
area() {
return this.sideLength * this.sideLength;
}
}
let square = new Square(5, 'blue');
console.log(`The ${square.color} square has an area of ${square.area()}.`);
在这个例子中,ColoredShape
接口继承自 Shape
和 Colorable
接口,Square
类实现了 ColoredShape
接口,需要实现 area
方法并提供 color
属性。
类的类型兼容性
在 TypeScript 中,类的类型兼容性是基于结构的。这意味着如果两个类具有兼容的结构,它们在类型上就是兼容的。
class Point {
x: number;
y: number;
}
class Position {
x: number;
y: number;
}
let point: Point = new Point();
let position: Position = new Position();
point = position;
position = point;
在上述代码中,Point
类和 Position
类具有相同的结构(都有 x
和 y
属性),因此它们在类型上是兼容的,可以相互赋值。
然而,如果类的结构不同,就会出现类型不兼容的情况。
class Circle {
radius: number;
}
class Rectangle {
width: number;
height: number;
}
let circle: Circle = new Circle();
let rectangle: Rectangle = new Rectangle();
// circle = rectangle; // 这将导致编译错误,因为结构不兼容
// rectangle = circle; // 这也将导致编译错误,因为结构不兼容
类的泛型
泛型是 TypeScript 中强大的特性,它允许我们在定义类时使用类型参数,使类能够处理不同类型的数据,同时保持类型安全。
class Box<T> {
value: T;
constructor(value: T) {
this.value = value;
}
getValue() {
return this.value;
}
}
let numberBox = new Box<number>(42);
let stringBox = new Box<string>('Hello, TypeScript');
console.log(numberBox.getValue());
console.log(stringBox.getValue());
在上述代码中:
- 定义泛型类:
Box
类使用类型参数T
,value
属性的类型为T
,构造函数接受一个类型为T
的参数。 - 使用泛型类:我们可以通过在类名后指定具体类型(如
Box<number>
和Box<string>
)来创建不同类型的Box
实例,分别用于存储数字和字符串。
类的装饰器
装饰器是一种特殊的声明,可以附加到类、方法、属性或参数上,用于修改或增强它们的行为。在 TypeScript 中,装饰器是一个函数,它接受目标(类、方法等)作为参数,并返回修改后的目标。
- 类装饰器:类装饰器应用于类的定义。
function logClass(target: Function) {
console.log(`Class ${target.name} has been logged.`);
return target;
}
@logClass
class MyClass {
message: string;
constructor(message: string) {
this.message = message;
}
showMessage() {
console.log(this.message);
}
}
let myObj = new MyClass('Hello from MyClass');
myObj.showMessage();
在上述代码中,logClass
是一个类装饰器,它在类定义时打印类名。@logClass
语法将装饰器应用到 MyClass
上。
- 方法装饰器:方法装饰器应用于类的方法。
function logMethod(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
let originalMethod = descriptor.value;
descriptor.value = function(...args: any[]) {
console.log(`Calling method ${propertyKey} with arguments:`, args);
let result = originalMethod.apply(this, args);
console.log(`Method ${propertyKey} returned:`, result);
return result;
};
return descriptor;
}
class MathOperations {
@logMethod
add(a: number, b: number) {
return a + b;
}
}
let mathOps = new MathOperations();
mathOps.add(3, 5);
在这个例子中,logMethod
是一个方法装饰器,它在方法调用前后打印日志信息,记录传入的参数和返回的结果。
- 属性装饰器:属性装饰器应用于类的属性。
function uppercase(target: any, propertyKey: string) {
let value = target[propertyKey];
const getter = function() {
return value.toUpperCase();
};
const setter = function(newValue: string) {
value = newValue;
};
if (delete target[propertyKey]) {
Object.defineProperty(target, propertyKey, {
get: getter,
set: setter,
enumerable: true,
configurable: true
});
}
}
class Person {
@uppercase
name: string;
constructor(name: string) {
this.name = name;
}
}
let person = new Person('alice');
console.log(person.name);
在上述代码中,uppercase
是一个属性装饰器,它将属性值转换为大写形式。当访问 name
属性时,会返回大写的字符串。
- 参数装饰器:参数装饰器应用于类方法的参数。
function validateNumber(target: any, propertyKey: string, parameterIndex: number) {
return function(...args: any[]) {
if (typeof args[parameterIndex]!== 'number') {
throw new Error(`Argument at index ${parameterIndex} must be a number.`);
}
return target.apply(this, args);
};
}
class Calculator {
@validateNumber
multiply(a: number, b: number) {
return a * b;
}
}
let calculator = new Calculator();
console.log(calculator.multiply(3, 5));
// calculator.multiply(3, 'five'); // 这将抛出错误
在这个例子中,validateNumber
是一个参数装饰器,它在方法调用前验证指定参数是否为数字类型,如果不是则抛出错误。
通过上述对 TypeScript 类和继承的各个方面的详细介绍,包括类的基础定义、访问修饰符、静态成员、继承、重写方法、抽象类与方法、混入、类与接口关系、类型兼容性、泛型以及装饰器等,希望读者对 TypeScript 在面向对象编程方面的强大功能有了更深入的理解和掌握。在实际开发中,可以根据具体需求灵活运用这些特性,构建出健壮、可维护且类型安全的代码。