大白话TypeScript第二章面向对象编程
第二阶段主要是学习面向对象编程相关的 TypeScript 知识,面向对象编程就像是把代码按照不同的“角色”或者“事物”来组织,每个“角色”有自己的特点和行为,这样能让代码更有条理,也更容易维护和扩展。下面详细介绍类和继承、接口这两部分内容。
1. 类和继承
类
类就像是一个模板,你可以用这个模板来创建很多个类似的对象。比如说,我们要描述“人”这个概念,人有名字、年龄,还会说话,那我们就可以创建一个“人”的类。
// 定义一个 Person 类
class Person {
// 类的属性,这里定义了 name 和 age 属性
name: string;
age: number;
// 构造函数,当你创建这个类的对象时会自动调用,用来初始化对象的属性
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
// 类的方法,这里定义了一个 sayHello 方法,用来让这个人打招呼
sayHello() {
console.log(`大家好,我叫 ${this.name},我今年 ${this.age} 岁了。`);
}
}
// 使用 Person 类创建一个对象,就像用模板做出一个具体的东西
let person1 = new Person('张三', 20);
// 调用对象的方法
person1.sayHello();
在上面的代码里,Person
就是一个类,它有 name
和 age
两个属性,还有一个 sayHello
方法。我们通过 new Person('张三', 20)
创建了一个 person1
对象,然后调用 sayHello
方法让这个人打了招呼。
继承
继承就像是儿子可以继承爸爸的一些特点和能力。在编程里,一个类可以继承另一个类的属性和方法,这样可以避免重复编写代码。
// 定义一个 Student 类,它继承自 Person 类
class Student extends Person {
// 新增一个属性,学生还有班级信息
classInfo: string;
// 构造函数,这里除了接收 name 和 age,还接收 classInfo
constructor(name: string, age: number, classInfo: string) {
// 使用 super 调用父类的构造函数,初始化父类的属性
super(name, age);
this.classInfo = classInfo;
}
// 重写父类的 sayHello 方法,让学生打招呼时多说一些信息
sayHello() {
console.log(`大家好,我叫 ${this.name},我今年 ${this.age} 岁了,我在 ${this.classInfo} 班级。`);
}
}
// 创建一个 Student 对象
let student1 = new Student('李四', 18, '高三一班');
// 调用重写后的 sayHello 方法
student1.sayHello();
在这个例子中,Student
类继承了 Person
类,所以 Student
对象也有 name
和 age
属性,还能调用 sayHello
方法。同时,Student
类又新增了 classInfo
属性,并且重写了 sayHello
方法,让打招呼的内容更丰富。
访问修饰符
访问修饰符就像是给类的属性和方法加上了不同的“锁”,控制它们在不同地方的访问权限。主要有三种:public
(公共的,谁都能访问)、private
(私有的,只有在类的内部能访问)、protected
(受保护的,在类的内部和子类中能访问)。
class Animal {
// public 属性,谁都能访问
public name: string;
// private 属性,只有在 Animal 类内部能访问
private age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
// 可以在类内部访问 private 属性
getAge() {
return this.age;
}
}
let animal1 = new Animal('小狗', 3);
console.log(animal1.name); // 可以访问 public 属性
// console.log(animal1.age); // 这里会报错,因为 age 是 private 属性
console.log(animal1.getAge()); // 通过类的方法访问 private 属性
2. 接口
接口就像是一份合同,规定了某个对象必须有哪些属性和方法。当一个对象要遵守这个“合同”时,就必须实现接口里规定的内容。
// 定义一个 PersonInterface 接口
interface PersonInterface {
// 规定必须有 name 属性,类型是 string
name: string;
// 规定必须有 age 属性,类型是 number
age: number;
// 规定必须有 sayHello 方法,返回值类型是 void(也就是没有返回值)
sayHello(): void;
}
// 定义一个类 Person2,实现 PersonInterface 接口
class Person2 implements PersonInterface {
name: string;
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
sayHello() {
console.log(`你好,我叫 ${this.name},我 ${this.age} 岁了。`);
}
}
// 创建一个 Person2 对象
let person2 = new Person2('王五', 22);
person2.sayHello();
在这个例子中,PersonInterface
接口规定了一个对象必须有 name
、age
属性和 sayHello
方法。Person2
类实现了这个接口,所以它必须按照接口的要求来定义这些属性和方法。这样做的好处是,代码的结构更清晰,也更容易进行团队协作和代码维护。
面向对象编程和面向过程编程有什么区别?
面向过程编程
面向过程编程就像是按照一份详细的菜谱做菜。你把做菜的过程拆分成一个一个步骤,然后按照顺序依次执行这些步骤,最终做出一道菜。每一个步骤都很明确,做完一个步骤再做下一个步骤,整个过程是线性的。
代码示例:计算两个数的和,然后将结果乘以 3
# 第一步:定义一个函数来计算两个数的和
def add_numbers(a, b):
return a + b
# 第二步:定义一个函数来将一个数乘以 3
def multiply_by_three(num):
return num * 3
# 第三步:按照顺序调用这些函数
num1 = 5
num2 = 3
sum_result = add_numbers(num1, num2)
final_result = multiply_by_three(sum_result)
print(final_result)
在这个例子中,我们把计算过程拆分成了几个独立的函数,每个函数完成一个特定的任务。然后按照顺序依次调用这些函数,最终得到我们想要的结果。整个程序的执行过程就像是沿着一条直线,一步一步地走下去。
面向对象编程
面向对象编程就像是组织一场演出。你有不同的角色,每个角色都有自己的特点和能力。你不用去关心每个角色具体是怎么完成任务的,只需要告诉他们要做什么。这些角色就像是一个个对象,他们封装了自己的数据和行为。
代码示例:同样是计算两个数的和,然后将结果乘以 3
class Calculator:
def __init__(self):
# 初始化一个属性来存储结果
self.result = 0
def add(self, a, b):
# 计算两个数的和,并将结果存储在 self.result 中
self.result = a + b
return self.result
def multiply_by_three(self):
# 将 self.result 乘以 3
self.result = self.result * 3
return self.result
# 创建一个 Calculator 对象
calc = Calculator()
# 调用 add 方法计算两个数的和
sum_result = calc.add(5, 3)
# 调用 multiply_by_three 方法将和乘以 3
final_result = calc.multiply_by_three()
print(final_result)
在这个例子中,我们创建了一个 Calculator
类,它就像是一个角色。这个角色有自己的属性 result
来存储计算结果,还有两个方法 add
和 multiply_by_three
来完成具体的计算任务。我们通过创建这个类的对象 calc
,然后调用对象的方法来完成计算。我们不用去关心这些方法内部是怎么实现的,只需要知道调用这些方法能得到我们想要的结果就行。
两者的区别总结
- 组织方式:
- 面向过程编程是按照步骤来组织代码的,就像做菜的步骤一样,一个接着一个。
- 面向对象编程是按照对象来组织代码的,每个对象有自己的属性和方法,就像演出中的每个角色有自己的特点和能力。
- 可维护性:
- 面向过程编程如果程序变得复杂,步骤会越来越多,代码会变得难以维护和扩展。就像菜谱越来越长,很难修改和添加新的步骤。
- 面向对象编程通过将数据和行为封装在对象中,使得代码的结构更清晰,更容易维护和扩展。就像演出中的角色可以独立更换和调整,不影响整个演出的进行。
- 复用性:
- 面向过程编程中的函数复用性相对较低,因为函数通常是针对特定的任务编写的。
- 面向对象编程中的对象和类可以很方便地复用,你可以创建多个相同类的对象,也可以继承一个类来创建新的类。就像演出中的角色可以在不同的演出中重复使用,或者基于一个角色创建新的角色。
- 加粗样式
有哪些常见的设计模式可以应用于面向对象编程?
以下为你介绍几种在 TypeScript 面向对象编程中常见的设计模式,同时结合大白话解释和代码示例。
1. 单例模式
大白话解释
单例模式就好比世界上独一无二的宝藏,在整个程序运行期间,这个“宝藏”只有一份。不管是谁,在什么时候去获取它,拿到的都是同一个东西。就像一个国家的总统,无论什么时候提到总统,指的都是同一个人。
代码示例
class President {
// 静态私有属性,用于存储单例实例
private static instance: President;
// 私有构造函数,防止外部直接实例化
private constructor() {}
// 静态方法,用于获取单例实例
public static getInstance(): President {
if (!this.instance) {
this.instance = new President();
}
return this.instance;
}
// 示例方法
public introduce(): void {
console.log("我是这个国家的总统。");
}
}
// 获取总统实例
const president1 = President.getInstance();
const president2 = President.getInstance();
// 检查两个实例是否相同
console.log(president1 === president2); // 输出: true
president1.introduce();
2. 工厂模式
大白话解释
工厂模式就像是一个神奇的工厂,你只要告诉工厂你想要什么产品,工厂就会按照要求生产出来给你,你不用关心产品具体是怎么制造的。就像你去蛋糕店,告诉店员你要一个巧克力蛋糕,店员就会从厨房拿出一个巧克力蛋糕给你,你不用知道蛋糕是怎么做出来的。
代码示例
// 定义蛋糕基类
abstract class Cake {
abstract taste(): void;
}
// 定义巧克力蛋糕类
class ChocolateCake extends Cake {
taste(): void {
console.log("这是巧克力蛋糕,味道浓郁。");
}
}
// 定义草莓蛋糕类
class StrawberryCake extends Cake {
taste(): void {
console.log("这是草莓蛋糕,味道清甜。");
}
}
// 定义蛋糕工厂类
class CakeFactory {
createCake(cakeType: string): Cake {
switch (cakeType) {
case "chocolate":
return new ChocolateCake();
case "strawberry":
return new StrawberryCake();
default:
throw new Error("不支持的蛋糕类型");
}
}
}
// 使用蛋糕工厂
const factory = new CakeFactory();
const chocolateCake = factory.createCake("chocolate");
const strawberryCake = factory.createCake("strawberry");
chocolateCake.taste();
strawberryCake.taste();
3. 观察者模式
大白话解释
观察者模式就像是一群粉丝在关注明星。明星的一举一动(状态变化)都会通知给粉丝,粉丝们根据明星的动态做出相应的反应。明星就是被观察的对象,粉丝就是观察者。
代码示例
// 定义观察者接口
interface Observer {
update(message: string): void;
}
// 定义具体观察者类(粉丝类)
class Fan implements Observer {
constructor(private name: string) {}
update(message: string): void {
console.log(`${this.name} 收到消息: ${message}`);
}
}
// 定义被观察对象类(明星类)
class Celebrity {
private observers: Observer[] = [];
// 添加观察者
addObserver(observer: Observer): void {
this.observers.push(observer);
}
// 移除观察者
removeObserver(observer: Observer): void {
const index = this.observers.indexOf(observer);
if (index !== -1) {
this.observers.splice(index, 1);
}
}
// 通知所有观察者
notifyObservers(message: string): void {
this.observers.forEach(observer => observer.update(message));
}
// 明星发布新状态
postStatus(status: string): void {
const message = `明星发布了新状态: ${status}`;
this.notifyObservers(message);
}
}
// 创建明星和粉丝对象
const celebrity = new Celebrity();
const fan1 = new Fan("张三");
const fan2 = new Fan("李四");
// 粉丝关注明星
celebrity.addObserver(fan1);
celebrity.addObserver(fan2);
// 明星发布新状态
celebrity.postStatus("今天很开心!");
4. 装饰器模式
大白话解释
装饰器模式就像是给人穿衣服,人本身有自己的基本属性和行为,但是可以通过穿上不同的衣服(装饰器)来增加额外的功能。比如一个人穿上雨衣就可以防雨,穿上防晒衣就可以防晒。
代码示例
// 定义人基类
class Person {
show(): void {
console.log("这是一个普通人。");
}
}
// 定义装饰器基类
abstract class Decorator {
constructor(protected person: Person) {}
abstract show(): void;
}
// 定义具体装饰器类(雨衣装饰器)
class RaincoatDecorator extends Decorator {
show(): void {
this.person.show();
console.log("穿上了雨衣,可以防雨。");
}
}
// 定义具体装饰器类(防晒衣装饰器)
class SunscreenDecorator extends Decorator {
show(): void {
this.person.show();
console.log("穿上了防晒衣,可以防晒。");
}
}
// 创建一个人对象
const person = new Person();
// 给人穿上雨衣
const raincoatPerson = new RaincoatDecorator(person);
// 再给穿上雨衣的人穿上防晒衣
const raincoatSunscreenPerson = new SunscreenDecorator(raincoatPerson);
raincoatSunscreenPerson.show();
这些设计模式在 TypeScript 面向对象编程中能帮助你让代码更灵活、可维护和可扩展。你可以根据具体的业务场景选择合适的设计模式。
如何优化面向对象程序的性能?
在 TypeScript 里优化面向对象程序的性能,就像是给一辆车做保养和升级,让它跑得更快、更稳、更省燃料。下面从几个常见的方面来详细说说怎么优化,还会配上代码示例。
1. 减少对象的创建
大白话解释
创建对象就像是造房子,每次造房子都得花不少时间和材料。要是频繁地创建对象,程序就会变慢。所以能不创建新对象就不创建,尽量复用已有的对象。
代码示例
没优化前:
class Point {
constructor(public x: number, public y: number) {}
}
function calculateDistance() {
// 每次调用函数都创建新的 Point 对象
const point1 = new Point(1, 2);
const point2 = new Point(3, 4);
const dx = point2.x - point1.x;
const dy = point2.y - point1.y;
return Math.sqrt(dx * dx + dy * dy);
}
// 多次调用函数,频繁创建对象
for (let i = 0; i < 1000; i++) {
calculateDistance();
}
优化后:
class Point {
constructor(public x: number, public y: number) {}
}
// 提前创建好对象,复用它们
const point1 = new Point(1, 2);
const point2 = new Point(3, 4);
function calculateDistance() {
const dx = point2.x - point1.x;
const dy = point2.y - point1.y;
return Math.sqrt(dx * dx + dy * dy);
}
// 多次调用函数,不再频繁创建对象
for (let i = 0; i < 1000; i++) {
calculateDistance();
}
2. 使用静态方法和属性
大白话解释
静态方法和属性就像是公共的工具和资源,不用创建对象就能用。如果一个方法或者属性和具体的对象没关系,就可以把它设成静态的,这样能避免每次创建对象都带上这些东西,节省内存。
代码示例
没优化前:
class MathUtils {
constructor() {}
// 非静态方法
add(a: number, b: number): number {
return a + b;
}
}
// 创建对象来调用方法
const mathUtils = new MathUtils();
const result = mathUtils.add(1, 2);
优化后:
class MathUtils {
// 静态方法
static add(a: number, b: number): number {
return a + b;
}
}
// 直接通过类名调用静态方法,不用创建对象
const result = MathUtils.add(1, 2);
3. 避免不必要的继承和多态开销
大白话解释
继承和多态就像是家族传承和分工,虽然很有用,但有时候用得太复杂会增加程序的负担。如果一个类不需要继承其他类或者使用多态,就别用,这样能让程序更简单、更快。
代码示例
没优化前:
// 基类
class Animal {
constructor(public name: string) {}
makeSound(): void {}
}
// 子类
class Dog extends Animal {
makeSound(): void {
console.log('汪汪汪');
}
}
// 如果只是想打印狗叫,没必要这么复杂的继承
const dog = new Dog('旺财');
dog.makeSound();
优化后:
// 直接定义一个简单的函数
function dogBark() {
console.log('汪汪汪');
}
// 直接调用函数
dogBark();
4. 合理使用缓存
大白话解释
缓存就像是一个小仓库,把经常要用的东西存起来,下次要用的时候直接从仓库里拿,不用再重新计算或者查找,这样能节省时间。
代码示例
class Fibonacci {
private cache: { [key: number]: number } = {};
// 计算斐波那契数列
calculate(n: number): number {
if (n in this.cache) {
return this.cache[n];
}
if (n <= 1) {
return n;
}
const result = this.calculate(n - 1) + this.calculate(n - 2);
this.cache[n] = result;
return result;
}
}
const fib = new Fibonacci();
// 第一次计算,会把结果存到缓存里
console.log(fib.calculate(10));
// 第二次计算,直接从缓存里拿结果,速度更快
console.log(fib.calculate(10));
通过这些方法,可以让你的 TypeScript 面向对象程序跑得更快、性能更好。