FP与OOP编程范式入门
一、编程范式概述
1.1 什么是编程范式
概念说明
编程范式(Programming Paradigm)代表着编写代码的方式或风格。不同的编程范式提供了不同的思维模式和代码组织方式。
三种主流范式
| 缩写 | 英文全称 | 中文含义 | 核心思想 |
|---|---|---|---|
| FP | Functional Programming | 函数式编程 | 以函数为核心,强调纯函数和数据不可变 |
| OOP | Object-Oriented Programming | 面向对象编程 | 以对象为核心,强调抽象、封装、继承、多态 |
| FRP | Functional Reactive Programming | 函数响应式编程 | 结合函数式和响应式,处理异步数据流 |
注意事项
- 编程范式不是互斥的,一个项目中可以混合使用多种范式
- 选择哪种范式取决于场景、团队能力和个人习惯
- 现代前端框架(Vue、React)偏向函数式编程风格
二、函数式编程(FP)
2.1 核心概念
概念说明
函数式编程将程序分解为一系列函数,每个函数完成特定的、独立的任务。强调数据的输入输出,避免副作用。
核心特点
code
函数式编程三大特征:
│
├── 确定的输入输出
│ ├── 相同输入必定得到相同输出
│ ├── 函数执行结果可预测
│ └── 便于测试和调试
│
├── 无副作用
│ ├── 不修改外部状态
│ ├── 不创建 DOM 节点
│ └── 不发起 HTTP 请求
│
└── 引用透明
├── 函数调用可被返回值替换
├── IDE 友好,易于跳转查看
└── 代码逻辑清晰易懂2.2 实战案例:用户登录表单
需求描述
实现一个用户登录表单:
- 输入用户名和密码
- 校验:用户名和密码必填,密码长度需大于 6 位
- 校验通过后打印用户信息并提示登录成功
- 校验失败提示"用户名或密码不符合要求"
面向过程实现(对比)
javascript
// 面向过程:代码耦合度高,难以复用
// 获取 DOM 节点
const form = document.querySelector('#loginForm');
const usernameInput = document.querySelector('#username');
const passwordInput = document.querySelector('#password');
// 绑定事件
form.addEventListener('submit', function(e) {
e.preventDefault(); // 阻止默认提交
// 读取输入值
const username = usernameInput.value;
const password = passwordInput.value;
// 校验逻辑
if (!username || !password || password.length <= 6) {
alert('用户名或密码不符合要求');
return;
}
// 处理数据
const user = { username, password };
console.log('用户信息:', user);
alert('登录成功');
});面向过程的问题:
- 所有逻辑写在一起,耦合度高
- 难以复用和扩展
- 不易维护和测试
函数式编程实现
javascript
// 函数式编程:逻辑清晰,易于复用和维护
// ========== 函数定义 ==========
/**
* 创建表单实例
* @param {string} formId - 表单 ID
* @param {Function} handler - 提交处理函数
*/
function createForm(formId, handler) {
const form = document.querySelector(`#${formId}`);
form.addEventListener('submit', (e) => {
e.preventDefault();
handler();
});
}
/**
* 获取用户输入
* @param {string} usernameId - 用户名输入框 ID
* @param {string} passwordId - 密码输入框 ID
* @returns {Object} 用户输入数据
*/
function getUserInput(usernameId, passwordId) {
const username = document.querySelector(`#${usernameId}`).value;
const password = document.querySelector(`#${passwordId}`).value;
return { username, password };
}
/**
* 创建用户对象
* @param {Object} input - 用户输入数据
* @param {boolean} needValidate - 是否需要校验
* @returns {Object|null} 用户对象或 null
*/
function createUser(input, needValidate = true) {
if (needValidate && !validate(input)) {
return null;
}
return {
username: input.username,
password: input.password,
createdAt: new Date().toISOString()
};
}
/**
* 校验用户输入
* @param {Object} input - 用户输入数据
* @returns {boolean} 校验结果
*/
function validate(input) {
const { username, password } = input;
// 可扩展的校验规则
const rules = [
{ check: () => !!username, message: '用户名不能为空' },
{ check: () => !!password, message: '密码不能为空' },
{ check: () => password.length > 6, message: '密码长度必须大于 6 位' }
];
return rules.every(rule => rule.check());
}
/**
* 提示用户
* @param {string} message - 提示消息
*/
function showMessage(message) {
alert(message);
}
// ========== 业务逻辑组合 ==========
/**
* 登录处理函数
*/
function submitHandler() {
// 1. 获取输入
const input = getUserInput('username', 'password');
// 2. 创建用户(带校验)
const user = createUser(input, true);
// 3. 处理结果
if (user) {
console.log('用户信息:', user);
showMessage('登录成功');
} else {
showMessage('用户名或密码不符合要求');
}
}
// ========== 初始化 ==========
createForm('loginForm', submitHandler);函数式编程优势:
| 优势 | 说明 |
|---|---|
| 逻辑清晰 | 每个函数职责单一,易于理解 |
| 易于复用 | createForm、validate 等函数可在多处使用 |
| 便于扩展 | 新增校验规则只需修改 validate 函数 |
| 易于维护 | 问题定位精准,修改某个函数不影响其他部分 |
| IDE 友好 | 函数跳转方便,代码提示完善 |
2.3 函数式编程最佳实践
javascript
// ========== 纯函数示例 ==========
// 纯函数:相同输入必定得到相同输出,无副作用
function add(a, b) {
return a + b;
}
function formatUserName(firstName, lastName) {
return `${firstName} ${lastName}`;
}
// 非纯函数:依赖外部状态,有副作用
let count = 0;
function increment() {
count++; // 修改外部状态
return count;
}
function logMessage(message) {
console.log(message); // 产生副作用(I/O 操作)
}
// ========== 不可变性示例 ==========
// 不可变操作:返回新对象,不修改原对象
const user = { name: 'Tom', age: 25 };
function updateUserAge(user, newAge) {
return { ...user, age: newAge }; // 返回新对象
}
const updatedUser = updateUserAge(user, 26);
console.log(user); // { name: 'Tom', age: 25 } - 原对象未变
console.log(updatedUser); // { name: 'Tom', age: 26 }
// 可变操作:直接修改原对象
function updateUserAgeMutable(user, newAge) {
user.age = newAge; // 直接修改原对象
return user;
}
// ========== 高阶函数示例 ==========
// 高阶函数:接收函数作为参数,或返回函数
function createValidator(rules) {
return function(input) {
return rules.every(rule => rule.check(input));
};
}
const passwordRules = [
{ check: (pwd) => pwd.length >= 8, message: '至少 8 位' },
{ check: (pwd) => /[A-Z]/.test(pwd), message: '包含大写字母' },
{ check: (pwd) => /[0-9]/.test(pwd), message: '包含数字' }
];
const validatePassword = createValidator(passwordRules);
console.log(validatePassword('abc123')); // false
console.log(validatePassword('Abcdefg123')); // true2.4 现代框架中的函数式编程
React Hooks
javascript
import React, { useState, useEffect } from 'react';
// React 函数组件 + Hooks:函数式编程思想
function UserForm() {
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
// 纯函数处理事件
const handleSubmit = () => {
const user = createUser({ username, password });
if (user) {
console.log('登录成功', user);
}
};
return (
<form onSubmit={handleSubmit}>
<input
value={username}
onChange={(e) => setUsername(e.target.value)}
/>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
<button type="submit">登录</button>
</form>
);
}Vue 3 Composition API
javascript
import { ref, computed } from 'vue';
// Vue 3 组合式 API:函数式编程思想
export default {
setup() {
const username = ref('');
const password = ref('');
// 纯函数校验
const isValid = computed(() => {
return username.value &&
password.value &&
password.value.length > 6;
});
const handleSubmit = () => {
if (isValid.value) {
console.log('登录成功', {
username: username.value,
password: password.value
});
}
};
return { username, password, isValid, handleSubmit };
}
};三、面向对象编程(OOP)
3.1 核心概念
概念说明
面向对象编程将程序分解为一系列对象,每个对象包含数据(属性)和行为(方法)。通过抽象、封装、继承、多态来组织代码。
三大特性
code
面向对象编程三大特性:
│
├── 封装性
│ ├── 属性和方法封装在类中
│ ├── 外部不能直接访问内部数据
│ └── 通过公共方法操作内部状态
│
├── 继承性
│ ├── 子类继承父类的属性和方法
│ ├── 避免代码重复
│ └── 建立类之间的层级关系
│
└── 多态性
├── 同一个方法名,不同对象有不同实现
├── 提高代码灵活性
└── 降低代码耦合度3.2 实战案例:用户登录表单(OOP 实现)
javascript
// ========== 类定义 ==========
/**
* 校验器类
* 提供静态校验方法
*/
class Validator {
/**
* 校验用户名和密码
* @param {string} username - 用户名
* @param {string} password - 密码
* @returns {boolean} 校验结果
*/
static validate(username, password) {
if (!username || !password) {
return false;
}
if (password.length <= 6) {
return false;
}
return true;
}
}
/**
* 用户类
* 封装用户数据和行为
*/
class User {
// 私有属性(ES2022 新特性)
#username;
#password;
constructor(username, password) {
this.#username = username;
this.#password = password;
}
// Getter 方法
get username() {
return this.#username;
}
get password() {
return this.#password;
}
/**
* 用户问候语
*/
greet() {
console.log(`欢迎用户:${this.#username}`);
}
/**
* 打印用户信息
*/
printInfo() {
console.log({
username: this.#username,
password: '******', // 脱敏显示
createdAt: new Date().toISOString()
});
}
}
/**
* 用户表单类
* 封装表单逻辑
*/
class UserForm {
// 私有属性
#form;
#usernameInput;
#passwordInput;
constructor(formId) {
// 初始化 DOM 引用
this.#form = document.querySelector(`#${formId}`);
this.#usernameInput = document.querySelector('#username');
this.#passwordInput = document.querySelector('#password');
// 绑定事件
this.#init();
}
// 私有方法:初始化
#init {
this.#form.addEventListener('submit', (e) => {
e.preventDefault();
this.#handleSubmit();
});
}
// 私有方法:提交处理
#handleSubmit {
const username = this.#usernameInput.value;
const password = this.#passwordInput.value;
// 使用 Validator 类进行校验
if (!Validator.validate(username, password)) {
alert('用户名或密码不符合要求');
return;
}
// 创建 User 对象
const user = new User(username, password);
// 调用 User 对象的方法
user.printInfo();
user.greet();
alert('登录成功');
}
}
// ========== 初始化 ==========
const loginForm = new UserForm('loginForm');OOP 优势:
| 特性 | 说明 | 示例 |
|---|---|---|
| 抽象 | 从具体问题中抽象出通用概念 | User、Validator、UserForm |
| 封装 | 数据和方法封装在类中 | 私有属性 #username、#password |
| 复用 | 类可以在多个地方实例化 | 创建多个表单:new UserForm('registerForm') |
| 扩展 | 通过继承扩展功能 | 可创建 AdminUser extends User |
| 多态 | 同一方法不同实现 | 不同用户类型的 greet() 方法 |
3.3 继承与多态示例
javascript
// ========== 基类:用户 ==========
class User {
constructor(name, email) {
this.name = name;
this.email = email;
}
// 基础问候方法
greet() {
return `你好,我是 ${this.name}`;
}
// 获取权限
getPermissions() {
return ['read'];
}
}
// ========== 子类:管理员 ==========
class AdminUser extends User {
constructor(name, email, department) {
super(name, email); // 调用父类构造函数
this.department = department;
}
// 重写(Override)父类方法 - 多态
greet() {
return `你好,我是管理员 ${this.name},负责 ${this.department}`;
}
// 扩展父类权限
getPermissions() {
return [...super.getPermissions(), 'write', 'delete'];
}
// 新增方法
banUser(user) {
console.log(`${this.name} 封禁了用户 ${user.name}`);
}
}
// ========== 子类:VIP用户 ==========
class VIPUser extends User {
constructor(name, email, level) {
super(name, email);
this.level = level;
}
// 多态:不同的问候实现
greet() {
return `你好,我是 VIP${this.level} 用户 ${this.name}`;
}
// VIP 特有方法
getDiscount() {
return this.level === 1 ? 0.9 : 0.8;
}
}
// ========== 使用示例 ==========
const normalUser = new User('小明', 'xiaoming@example.com');
const admin = new AdminUser('张三', 'zhangsan@example.com', '技术部');
const vip = new VIPUser('李四', 'lisi@example.com', 2);
// 多态:同一个方法,不同的行为
console.log(normalUser.greet()); // 你好,我是 小明
console.log(admin.greet()); // 你好,我是管理员 张三,负责 技术部
console.log(vip.greet()); // 你好,我是 VIP2 用户 李四
// 继承:复用父类功能
console.log(normalUser.getPermissions()); // ['read']
console.log(admin.getPermissions()); // ['read', 'write', 'delete']
// 扩展:子类特有功能
admin.banUser(normalUser); // 张三 封禁了用户 小明
console.log(vip.getDiscount()); // 0.83.4 OOP 在前端框架中的应用
NestJS(后端框架)
typescript
// NestJS 典型的 OOP 风格
@Controller('users')
export class UserController {
// 依赖注入
constructor(private readonly userService: UserService) {}
@Get()
getUsers() {
return this.userService.findAll();
}
@Post()
createUser(@Body() createUserDto: CreateUserDto) {
return this.userService.create(createUserDto);
}
}
@Injectable()
export class UserService {
constructor(
@InjectRepository(User)
private userRepository: Repository<User>,
) {}
findAll(): Promise<User[]> {
return this.userRepository.find();
}
create(createUserDto: CreateUserDto): Promise<User> {
const user = this.userRepository.create(createUserDto);
return this.userRepository.save(user);
}
}Angular(前端框架)
typescript
// Angular 完全面向对象
@Component({
selector: 'app-user',
template: `
<div *ngFor="let user of users">
{{ user.name }}
</div>
`
})
export class UserComponent implements OnInit {
users: User[] = [];
// 依赖注入
constructor(private userService: UserService) {}
ngOnInit() {
this.loadUsers();
}
loadUsers() {
this.userService.getUsers().subscribe(users => {
this.users = users;
});
}
}四、函数响应式编程(FRP)
4.1 核心概念
概念说明
函数响应式编程结合了函数式编程和响应式编程的思想,主要用于处理异步数据流和事件流。
核心特点
code
函数响应式编程特征:
│
├── 数据流驱动
│ ├── 一切皆数据流
│ ├── 数据变化自动传播
│ └── 声明式编程风格
│
├── 异步场景优化
│ ├── 处理复杂的事件组合
│ ├── 用户输入防抖/节流
│ └── API 请求流式处理
│
└── 组合能力强
├── 多个数据流合并
├── 数据转换链式调用
└── 错误处理统一管理4.2 应用场景
场景一:表单实时校验
javascript
// 使用 RxJS 实现输入框实时校验
import { fromEvent } from 'rxjs';
import { map, debounceTime, distinctUntilChanged, filter } from 'rxjs/operators';
const usernameInput = document.querySelector('#username');
const submitBtn = document.querySelector('#submitBtn');
// 创建输入事件流
fromEvent(usernameInput, 'input')
.pipe(
map(event => event.target.value), // 提取输入值
debounceTime(300), // 防抖:300ms
distinctUntilChanged(), // 去重:值相同时不触发
filter(value => value.length >= 3), // 过滤:长度 >= 3
map(value => validateUsername(value)) // 校验
)
.subscribe(result => {
submitBtn.disabled = !result.isValid;
console.log(result.message);
});
function validateUsername(username) {
if (username.length < 3) {
return { isValid: false, message: '用户名至少 3 个字符' };
}
if (!/^[a-zA-Z0-9_]+$/.test(username)) {
return { isValid: false, message: '只能包含字母、数字、下划线' };
}
return { isValid: true, message: '用户名可用' };
}场景二:好友推荐列表(课堂案例)
需求描述:
- 显示好友推荐列表(3 条)
- 点击删除按钮移除一条推荐
- 删除后自动请求新数据填充
- 类似 QQ/微信的好友推荐功能
javascript
import { fromEvent, from } from 'rxjs';
import { mergeMap, scan, startWith } from 'rxjs/operators';
// 模拟 API 请求
function fetchRecommendation() {
return from(
fetch('/api/recommendation')
.then(res => res.json())
.then(data => data.user)
);
}
// 删除事件流
const deleteClick$ = fromEvent(document, 'click')
.pipe(
filter(event => event.target.classList.contains('delete-btn')),
mergeMap(() => fetchRecommendation()) // 删除后立即请求新数据
);
// 订阅删除事件
deleteClick$.subscribe(newUser => {
console.log('新推荐用户:', newUser);
// 更新 UI
updateRecommendationList(newUser);
});4.3 RxJS 核心概念
javascript
import { Observable, from, of, interval, fromEvent } from 'rxjs';
import { map, filter, mergeMap, take, switchMap } from 'rxjs/operators';
// ========== Observable(可观察对象)==========
// 创建 Observable
const observable$ = new Observable(subscriber => {
subscriber.next(1);
subscriber.next(2);
subscriber.next(3);
subscriber.complete();
});
// 订阅 Observable
observable$.subscribe({
next: value => console.log(value),
complete: () => console.log('完成')
});
// 输出:1, 2, 3, 完成
// ========== 常用创建操作符 ==========
// from:从数组/Promise 创建
const fromArray$ = from([1, 2, 3, 4, 5]);
// of:创建单个值
const singleValue$ = of('Hello');
// interval:定时发送递增值
interval(1000) // 每秒发送 0, 1, 2, 3...
.pipe(take(5)) // 只取前 5 个
.subscribe(value => console.log(value));
// ========== 常用管道操作符 ==========
from([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
.pipe(
filter(num => num % 2 === 0), // 过滤偶数
map(num => num * 10), // 乘以 10
take(3) // 只取前 3 个
)
.subscribe(value => console.log(value));
// 输出:20, 40, 60
// ========== 实战:搜索建议 ==========
const searchInput = document.querySelector('#search');
fromEvent(searchInput, 'input')
.pipe(
map(event => event.target.value),
debounceTime(500), // 防抖 500ms
distinctUntilChanged(), // 去重
filter(query => query.length > 0),
switchMap(query => // 切换到最新的搜索请求
from(fetch(`/api/search?q=${query}`).then(res => res.json()))
)
)
.subscribe(results => {
console.log('搜索结果:', results);
// 渲染搜索建议列表
renderSuggestions(results);
});4.4 Vue 3 中的响应式
javascript
import { ref, computed, watch, watchEffect } from 'vue';
// Vue 3 响应式系统:简化版的 FRP
export default {
setup() {
// 响应式数据(数据流)
const count = ref(0);
const doubleCount = computed(() => count.value * 2);
// 自动追踪依赖的副作用(响应式)
watchEffect(() => {
console.log(`count 变化了:${count.value}`);
});
// 监听特定数据源
watch(count, (newVal, oldVal) => {
console.log(`count 从 ${oldVal} 变为 ${newVal}`);
});
return { count, doubleCount };
}
};五、三种编程范式对比
5.1 核心对比
| 维度 | 函数式编程(FP) | 面向对象编程(OOP) | 函数响应式编程(FRP) |
|---|---|---|---|
| 核心思想 | 函数 + 数据流 | 对象 + 封装 | 数据流 + 响应式 |
| 代码组织 | 函数组合 | 类和对象 | Observable + 操作符 |
| 状态管理 | 不可变数据 | 可变状态(封装) | 流式传播 |
| 适用场景 | 数据转换、逻辑复用 | 业务建模、大型应用 | 异步事件、实时数据 |
| 学习曲线 | 中等 | 较陡 | 较陡 |
| 框架代表 | React、Vue 3 | Angular、NestJS | RxJS、RxJava |
| 优势 | 逻辑清晰、易测试 | 易于建模、结构化 | 异步处理能力强 |
| 劣势 | 业务建模不直观 | 继承滥用、耦合 | 学习成本高 |
5.2 如何选择
code
编程范式选择指南:
│
├── 选择函数式编程(FP)当:
│ ├── 需要大量数据转换和处理
│ ├── 希望代码易于测试和维护
│ ├── 使用 React/Vue 等现代框架
│ └── 团队偏好声明式编程风格
│
├── 选择面向对象编程(OOP)当:
│ ├── 业务逻辑复杂,需要建模
│ ├── 大型企业级应用
│ ├── 使用 Angular/NestJS 等框架
│ └── 团队有 Java/C# 背景
│
└── 选择函数响应式编程(FRP)当:
├── 处理复杂的异步事件流
├── 实时数据更新场景
├── 需要灵活的事件组合
└── 使用 RxJS/RxJava 等库5.3 实际项目中的混合使用
javascript
// ========== 混合使用示例 ==========
// OOP:定义类和对象
class UserService {
private users: User[] = [];
// FP:纯函数处理数据
private validateUser(user: User): boolean {
return !!user.name && user.age >= 0;
}
// FP:不可变操作
addUser(user: User): User[] {
if (!this.validateUser(user)) {
throw new Error('Invalid user');
}
this.users = [...this.users, user]; // 不可变更新
return this.users;
}
// FP:函数组合
getAdultUsers(): User[] {
return this.users
.filter(user => user.age >= 18)
.map(user => ({ ...user, isAdult: true }));
}
}
// FRP:处理异步事件
fromEvent(searchInput, 'input')
.pipe(
debounceTime(300),
map(event => event.target.value),
switchMap(query => userService.search(query))
)
.subscribe(results => {
console.log('搜索结果:', results);
});六、常见问题与解决方案
| 问题 | 原因 | 解决方案 |
|---|---|---|
| FP 代码难以理解 | 过度使用高阶函数和组合 | 保持函数简单,添加清晰的命名和注释 |
| OOP 类继承层次过深 | 过度使用继承 | 优先使用组合而非继承(Composition over Inheritance) |
| FP 无状态导致频繁传参 | 避免副作用过度 | 适当使用闭包或模块级状态 |
| OOP 类之间耦合严重 | 职责划分不清 | 遵循单一职责原则,使用依赖注入 |
| FRP 代码调试困难 | 数据流复杂 | 使用 RxJS 的 tap 操作符打印中间状态 |
| 不知道选哪种范式 | 对场景理解不足 | 从简单开始,逐步混合使用,根据场景调整 |
| FP 性能问题 | 频繁创建新对象 | 对于大数据量,考虑使用不可变数据库(如 Immer) |
| OOP 灵活性不足 | 类设计过于僵化 | 使用接口和抽象类,增加扩展点 |
七、学习要点总结
核心要点
- 编程范式是工具:没有最好的范式,只有最适合场景的范式
- 函数式编程核心:纯函数、不可变性、函数组合、无副作用
- 面向对象编程核心:抽象、封装、继承、多态
- 函数响应式编程核心:数据流、异步处理、声明式
- 混合使用:现代项目中通常会混合使用多种范式
行动建议
code
学习路径:
│
├── 第一阶段:理解概念(1 周)
│ ├── 理解三种范式的核心思想
│ ├── 完成课堂中的登录表单示例
│ └── 对比不同实现的优劣
│
├── 第二阶段:实战练习(2 周)
│ ├── 用 FP 重构现有代码中的工具函数
│ ├── 用 OOP 设计一个简单的业务模块
│ └── 用 RxJS 实现搜索建议功能
│
└── 第三阶段:框架应用(持续)
├── React/Vue 中应用 FP 思想
├── NestJS 中应用 OOP 思想
└── 根据场景灵活选择范式八、延伸学习资源
官方资源
推荐书籍
- 《JavaScript 函数式编程》
- 《JavaScript 设计模式》
- 《JavaScript 高级程序设计》第 8 章:对象、类与面向对象编程
练习建议
- 练习 1:将现有项目中的工具函数改造成纯函数
- 练习 2:设计一个购物车类,应用封装、继承、多态
- 练习 3:使用 RxJS 实现一个自动完成的搜索框
- 练习 4:对比同一个需求用 FP 和 OOP 实现的差异
- 练习 5:在 React 项目中应用函数式编程思想(自定义 Hooks)
延伸思考
code
思考题:
│
├── 为什么 Vue 3 和 React 都偏向函数式编程?
├── 为什么 NestJS 和 Angular 选择面向对象?
├── 在什么场景下应该使用 FRP?
└── 如何在团队中推广最佳实践?附录:编程范式速查表
| 概念 | FP | OOP | FRP |
|---|---|---|---|
| 基本单元 | 函数 | 类/对象 | Observable |
| 数据 | 不可变 | 可变(封装) | 流式 |
| 状态 | 无状态 | 对象状态 | 自动传播 |
| 控制流 | 函数组合 | 方法调用 | 操作符链 |
| 复用方式 | 函数复用 | 继承/组合 | 操作符复用 |
| 核心原则 | 纯函数、无副作用 | 封装、继承、多态 | 响应式、声明式 |