抽象化编程(Abstraction in Programming)是面向对象编程(OOP)中一个非常重要的设计原则,它与我们前面讨论的封装、继承、多态紧密相关。
抽象化编程的含义
核心思想: 关注于做什么(What),而不是如何做(How)。
抽象化意味着隐藏对象的复杂实现细节,只向用户暴露必要的功能和接口。它允许你在高层次上思考问题和设计系统,而不用被底层细节所困扰。
简单比喻
- 驾驶汽车: 抽象化就是方向盘、油门、刹车。你只需要知道踩油门能加速(做什么),而不需要知道引擎内部复杂的燃油喷射和点火过程(如何做)。
抽象化在代码中的体现
你的代码从 版本 1(过程式)升级到 版本 2(面向对象)的过程,就是一个典型的抽象化过程:
1. 操作抽象化
细节(如何做) | 抽象(做什么) |
---|---|
H1 = np.dot(batch_images, w1) + b1 |
x = layer.forward(x) |
dw1 = np.dot(batch_images.T, G1) |
grad = layer.backward(grad) |
在版本 2 中,你通过统一的接口 layer.forward(x)
抽象了所有层的前向计算。当你遍历 layers
数组时,你只需要知道你正在让数据通过当前层,而不需要关心当前层是矩阵乘法、激活函数还是丢弃操作。
2. 数据抽象化
你将权重(w)和偏置(b)这些数据抽象化并封装在 Linear
类内部。
- 版本 1: 权重 (
w1
,w3
,w4
) 是全局变量,代码的任何部分都可以访问和修改它们,它们是具体的。 - 版本 2: 权重 (
self.w
) 被封装在Linear
实例中,外部代码只能通过layer.backward()
方法来间接更新它们。权重被抽象为层内部的状态。
抽象化的主要优点
- 简化设计 (Simplicity): 提高了代码的可读性。当你在主循环中看到
for layer in layers: x = layer.forward(x)
时,你一眼就能明白这是前向传播,无需阅读每一行的矩阵乘法细节。 - 提高复用性 (Reusability): 一旦你定义了
Linear
或Sigmoid
这样的抽象模块,你可以将它们以任何顺序组合,来搭建不同的网络结构,无需重写底层逻辑。 - 便于维护 (Maintainability): 当你需要优化
Linear
层的性能(例如,改用其他矩阵库)时,你只需要修改Linear
类内部的实现细节,而无需修改主循环中的前向和反向传播代码。
抽象化是编写大规模、复杂系统时必不可少的工具,它让你的代码能够从“一团糟”的过程脚本,变成结构清晰、易于管理的模块化系统。