编程范式|青训营笔记

100 阅读5分钟
  • 阅读本篇文章,你将了解到,编程范式的基本概念,什么是面向对象,五大原则有哪些,怎么实现的五大原则,以及这些概念中的一些小知识点的温习。

请根据目录查看你需要的部分

概念

什么是编程范式?

  • 编程范式指的是一种编程的思想模式和方式,是用来描述解决问题的方法和过程的规范

常见的编程范式包括?

  1. 面向过程编程 (Procedural Programming)
  2. 面向对象编程 (Object-Oriented Programming)
  3. 函数式编程 (Functional Programming)
  4. 逻辑式编程 (Logic Programming)
  5. 并发式编程 (Concurrent Programming)

面向对象

五大原则

  • 单一职责原则

一个类或模块只负责一项职责或功能,避免类或模块功能过于复杂,难以维护和扩展。

  • 开放封闭原则

对扩展开放,对修改封闭,通过抽象化和多态化的设计,使得系统可以在不修改原有代码的情况下进行扩展。

  • 里氏替换原则

所有引用基类对象的地方,都能够透明地使用其子类对象,保证程序的可扩展性和可维护性。

  • 依赖倒置原则

一个类不应该依赖不需要的接口,避免类之间的依赖关系过于紧密,提高系统的灵活性和可扩展性。

  • 接口分离原则

高层模块不应该依赖低层模块,二者都应该依赖其抽象,通过抽象化和接口编程,实现模块之间的解耦。


原则的疑问

什么是基类?

  • 基类是指一个类或对象的基础类或父类
  • 是一个通用的模板,用于定义一个或多个子类所共有的属性和方法,同时也可以定义一些子类必须实现的抽象方法

什么是高层模块和底层模块?

  • 高层模块是指在系统中处于上层的模块
  • 负责业务逻辑和流程控制,对外提供接口
  • 高层模块可以调用底层模块的接口
  • 底层模块是指在系统中处于下层的模块
  • 负责底层的功能实现,例如与数据库、网络通信等底层的操作

高层模块不应该直接依赖于底层模块,而是应该依赖于抽象接口.

抽象接口不应该依赖于具体实现,而是应该让具体实现依赖于抽象接口


原则实现

举一个接口分离原则和单一职责原则同时满足的例子

// 定义多个接口,每个接口只包含必要的属性和方法
interface Readable {
  read(): void;
}
interface Writable {
  write(): void;
}
interface Configurable {
  setConfig(): void;
}

定义了三个接口 ReadableWritableConfigurable,每个接口只包含必要的属性和方法

// Bad example: 一个类依赖了不必要的接口
class BadPrinter implements Readable, Writable, Configurable {
  read() {
    // ...
  }
  write() {
    // ...
  }
  setConfig() {
    // ...
  }
}

BadPrinter 类中,我们让它实现了三个接口,即使它不需要某些接口。

// Good example: 将接口拆分成更小的粒度,每个类只实现必要的接口
class GoodPrinter implements Readable, Writable {
  read() {
    // ...
  }
  write() {
    // ...
  }
}
class ConfigurablePrinter implements Writable, Configurable {
  write() {
    // ...
  }
  setConfig() {
    // ...
  }
}

而在 GoodPrinterConfigurablePrinter 类中,我们根据实际需求只让它们实现了必要的接口,避免了不必要的依赖。

  • 因此,接口分离原则可以让我们更好地设计出松耦合的接口和类,提高代码的可维护性和可扩展性。
举一个依赖倒置原则的例子

在依赖注入中,依赖倒置的原则是通过把一个对象所依赖的对象的创建和管理过程交给外部的容器来实现的。这样,对象只需要依赖于一个接口,而不需要依赖于具体的实现,从而实现了对象之间的松耦合。

解释:耦合和解耦
  • 耦合指的是两个或多个模块之间的相互依赖程度
  • 当两个模块之间的依赖关系越强,耦合度就越高,反之则越低。
  • 解耦指的是减少模块之间的相互依赖程度,使得模块之间更加独立和自治。

常见的解耦方式有哪些?

  • 接口抽象:通过定义接口来解耦模块之间的依赖关系,使得模块只依赖于接口而不依赖于具体实现。
  • 控制反转:通过将对象创建和管理的责任交给外部容器来解耦模块之间的依赖关系,从而实现松耦合的系统设计。
  • 事件驱动:通过事件监听和触发机制来解耦模块之间的依赖关系,从而实现松耦合的消息传递和处理。

以下是一个 TypeScript 实现的简单示例:

interface ILogger {
  log(message: string): void;
}

首先定义了一个 ILogger 接口,表示一个简单的日志记录器。

class ConsoleLogger implements ILogger {
  log(message: string) {
    console.log(message);
  }
}

创建了一个 ConsoleLogger 类,实现了 ILogger 接口,并将日志记录到控制台。

class UserService {
  private logger: ILogger;

  constructor(logger: ILogger) {
    this.logger = logger;
  }
  addUser(username: string) {
    this.logger.log(`Adding user ${username}...`);
    // 添加用户逻辑
  }
}

创建了一个 UserService 类,它需要一个 ILogger 实例作为构造函数参数,并在 addUser 方法中调用了 ILogger 实例的 log 方法来记录日志。

// 创建控制反转容器,并将依赖注入到 UserService 中
const logger = new ConsoleLogger();
const userService = new UserService(logger);

// 调用 UserService 的方法
userService.addUser("Alice");

使用依赖注入的方式,将一个 ConsoleLogger 实例注入到 UserService 中,从而实现了依赖倒置。

优点: 这样,我们可以方便地更换日志记录器的实现,或者使用其他的实现方式,而不需要修改 UserService 类的代码。