Files
obsidian-notes/InBox/milky_BV1ArVU62Eac.md
2026-06-09 10:48:33 +08:00

14 KiB
Raw Blame History

title, source, date, tags, email_id
title source date tags email_id
[Milky] 为您整理《面向架构编程范式超越OOP和MVC》笔记 | BV1ArVU62Eac milky@4ueo.com 2026-06-09 10:48
milky
bilibili
notes
2062

Milky 为您整理了《面向架构编程范式超越OOP和MVC》 | BV1ArVU62Eac 笔记。

面向架构编程范式:超越 OOP 和 MVC

一、编程范式的本质

1.1 什么是编程范式

编程范式Programming Paradigm是指程序的表达方式它不决定程序能实现什么功能只决定能否方便、易懂地表达功能。

` 过程式表达: f1(); f2(); f3();

面向对象表达: obj.f1(); obj.f2(); obj.f3(); `

两段代码功能完全相同,都是先执行 F1再执行 F2、F3。表达方式不同但实现的功能是一样的。

关键结论: 面向对象能写的程序,过程式也能写 反过来也一样 无论用哪种范式,你的程序都能实现买东西这个功能

1.2 编程范式的真正目的

编程范式的根本目的是为了大规模代码和大规模团队分工合作。

当代码量膨胀到: 成千上万行 几十万行 上百万行

当团队有上百号程序员时,需要将整个项目拆分成多个模块,每个组负责一个模块。程序员需要调用其他人开发的模块,这就引出了模块封装隔离的概念。

1.3 模块封装隔离的概念

把模块的运行原理封装在模块内,只暴露接口。模块的调用者只需要会操作接口,不需要理解模块内部的运行原理,就能使用这个模块。

如果做不好封装隔离: 调用其他模块时,需要充分理解那个模块的内部原理 整个项目可能有成百上千个模块 需要懂上百个模块的内部原理,才能写自己的程序 这在实际项目中是不现实的

二、面向对象与模块封装

2.1 面向对象的封装优势

面向对象提供 class 语法,程序员可以很方便地把程序拆分成多个部分:

`python class A: def interface1(self): ... def interface2(self): ...

class B: def interface1(self): ... def interface3(self): ...

class C: def interface2(self): ... def interface3(self): ... `

每个类是一个模块,每个模块下有若干接口(可外部调用的函数)。实际项目中,每个类可能有几百上千行代码,好几十个函数接口,分别由不同程序员开发。

2.2 不只是面向对象在做模块封装

`python 过程式模块封装 module_a.py def f1(): ... def f2(): ...

module_b.py def f3(): ...

main.py import module_a import module_b module_a.f1() module_b.f3() `

效果和 class 一样。但这种方案太笨重了,比用类要复杂。

2.3 工程成本原则:"太麻烦,没人用"

工程问题和数学问题不同: 数学算法不需要考虑成本 工程问题最看重的就是成本

如果一个东西的使用成本大于收益,那根本就不会有人去用它。即使这是个好东西。

这会导致: 只有那些最重要的功能才能采用该方案 一些边边角角的小功能则完全没法用 用它反而会让程序更复杂

结论:面向对象比过程式高级,因为面向对象提供了一种方便的用于封装接口的语法。

2.4 class 的本质澄清

class 的本质是借口Interface

一个 class 定义得好不好,取决于: 有没有做好功能的封装 有没有暴露出正确的接口

至于汽车有几个轮子、像不像鸭子,根本无关紧要。

正确的类比:手机开机按钮就是一个接口。按下开机键后,手机进行一系列复杂的初始化过程,而用户不需要懂这个过程,只要会按开机键就可以了。

三、面向对象的设计缺陷

3.1 继承的问题:代码无法复用

使用继承进行代码复用的问题:

A / \ B C

假设 F1、F2、F3 的代码写在 A 类中。用户只需要 F1 和 F3无法复用这段代码——因为继承会把 F2 也带进来。

3.2 组合优于继承

组合思想: Main / | \ A B C

子模块拆分成 A 类、B 类、C 类。写 Main 类的程序员需要 F1、F2、F3 中的哪个就组哪个,不需要的就不组。既简单又灵活,完胜继承链条。

组合是一种思想,并不局限于某种特定语法。

3.3 多重继承的致命问题:重名冲突

这是一个记账器例子,子模块 A 和子模块 C 有一个重名的函数 F1这会造成冲突。

有人会说:把其中一个函数重命名为其他名字不就得了?

现实项目里没这么简单: A 模块和 C 模块可能分别是两家公司开发的开源库 每个库可能有几万行代码 F1 函数的调用链可能非常复杂 如果要改名,就要改动整条调用链 项目还可能存在其他库依赖这两个库的命名 要改的话就要连同其他库一起改

真正要命的是:原库可能已经在业界运行过多年,安全性和稳定性有保证。改了源代码就要承担出 bug 的风险。

3.4 这种改动要求是不合理的

对比其他领域的例子: 显卡上有一个电容 主板上的电容可能和显卡上的电容重名或型号相同 不会产生命名冲突

工业产品的拆分逻辑都是组合。子模块中存在重名零件,根本不影响任何东西。这才是正确的封装逻辑。

全天底下各行各业,就面向对象搞特殊。

四、MVC 方案的局限

4.1 MVC 就是组合

MVC 把功能拆分成最细的 Model数据和 View函数再通过 Controller 组合起来:

python class MainController: def init(self): self.modelA = ModelA() self.modelB = ModelB() self.viewC = ViewC()

用哪个 model 或 view 函数就组哪个,不用的就不组。

虽然 A 和 B 存在重名函数,但是并不会产生冲突。

4.2 MVC 的问题:抽象泄露

在 Main 模块中初始化 A 和 B并交换两者的指针时存在一个问题

A 和 B 的初始化逻辑泄露到 Main 这一层了。

在实际项目中: A 和 B 可能是别的程序员写的底层库 Main 模块由业务层程序员写 底层库的逻辑本不应该泄露到业务层

正确的封装:每一层程序员将本层的逻辑封装在本层。不应该要求使用者理解本层逻辑,只要使用者会用即可。

4.3 初始化逻辑的复杂性

实际项目中初始化可能存在非常复杂的交互关系:

A 先初始化几步 把中间结果传给 B 初始化 再把中间结果传回给 A继续初始化

另外,实际项目中子模块可能不止两个,很可能存在多个子模块初始化过程相互交织的情况。这要求 Main 程序员必须非常理解 A 和 B 等子模块的内部实现,才能写出 Main 层。

4.4 抽象泄露导致的问题

问题 1模块无法替换

库存程序员无法写出一个模块 C 去替换模块 B因为 Main 层耦合了模块 B 的初始化逻辑。模块 C 只要初始化逻辑稍有不同,就无法替换 B。

问题 2中间层方案要么灵活性差要么代码极为复杂

一种方案是加一个中间层Middle Layer中间层由 A 或 B 这一层的程序员编写。Main 层程序员无需理解中间层的内部实现。

但存在两种情况: 中间层代码最为简单但灵活性差Main 程序员无法选择组合哪几个子模块或者组合其他同接口的子模块。比如 Main 实现不了组合 A、C、D 三个模块,因为中间层只实现了 A、B 组合 让中间层适配,由 Main 层程序员自由选择组合哪些子模块:中间层的代码变得极为复杂

记住"太麻烦,没人用"原则。

4.5 依赖注入和微服务

依赖注入或微服务确实解决了抽象泄露问题。这两个方案都能进行模块替换。

衡量封装得好不好,可以看此模块能不能替换成同类模块。

唯一的问题是:这俩方案太重型了。使用成本太高。

五、时间悖论问题

5.1 时间悖论的根源

回到第一种方案,分析根本原因:

A 模块 ←→ B 模块 ↓ Main 模块

这里存在一个时间悖论: A 模块和 B 模块是先写出来的 Main 模块是在之后的某个时间点写出来的

如果想避免抽象泄露到 Main 层,程序员需要在 Main 类出现前表达交换 A 和 B 的指针这件事。然而,这做不到,因为 Main 只有在 A 和 B 存在后才出现。

5.2 语法限制导致的困境

但这是因为语法限制。假设编译器支持使用抽象名称作为指针名,其实是不存在时间悖论的。

六、新语法设计方案

6.1 核心语法设计

语法基本沿袭继承,但在调用函数或成员变量时,使用该成员的整个名称链:

符号 含义
..(两个点) 向上级寻找
...(三个点) 向平级寻找
.(一个点) 向下级寻找

也可以使用上斜杠、横杠和下斜杠表示。符号不重要,重要的是原理。

6.2 use 关键字

当名称列太长时,可以使用 use 关键字进行缩写:

python use module_a.b.c as abc

实际上,这个 use 关键字本质上和传统语言里包管理里的 include 或 import 是相同的东西。

6.3 super 关键字

代码中有个 super 关键字。ABCD 模块可能是先写出来的Main 模块是未来的某个时间点写出来的。

所以写 ABCD 的时候,程序员是不知道未来那个上级模块叫什么名字。这时可以用 super 关键字表示任意名称的上级模块:

python class A: def method(self): super..call_something() # 向上级寻找

在这种写法下: 底层模块的逻辑封装在底层 每一层程序员无需理解底层模块,只要会组合就行了 可以任意替换同类模块

6.4 from 和 to 关键字

python from ModuleC import xxx as xxx_renamed to ModuleX use some_function

from 和 to 关键字比看上去更重要,类似于主板设计师在 PCB 板上连同导线。这个动作是本编程范式下的基石。

6.5 模块重命名

有时子模块可能需要同类。那么可以像下面这样重命名:

python class MainB: from ModuleA as mod_a from ModuleB as mod_b # 两个模块都有相同的接口,但名称不同时进行重命名

6.6 新语法的优势

在抽象泄露的情况下,是无法做到自由替换子模块的,因为上级模块耦合了下层实现。每个下层实现的逻辑不同,替换时要连带替换,泄露到内层的逻辑代码。

使用新语法后: 每层程序员可以任意组合 B 或 C 两者名称不同时,使用 from 关键字重命名即可 模块可以自由替换

七、核心原理:工业流水线思想

7.1 职能隔离的比喻

本语法的核心思想是仿照工业流水生产线生产工业品:

不同工段的工人是职能隔离的 不同工段的工人无需理解前一个工段 只需要会组装上一个工段传输过来的零件即可 同时,本工段的工人也应该将封装好的零件提供给后一个工段的工人,让其无需理解本工段逻辑

7.2 空间位置关系

拿主板和显卡的例子来做说明: 主板上的一个电容和显卡上的一个电容重名或型号相同时,不会产生名称冲突 原因在于,主板和显卡是通过三维空间位置寻找子零件

本语法中的全路径巡境表达的是对象成员的空间位置关系,相当于主板设计师在 PCB 板上按空间位置连同导线。

7.3 编译期依赖注入

本语法相当于在面向对象的继承语法的基础上,在编译期实现依赖注入。

本语法是对面向对象的语法的发展或改进,而非否定。

八、API 关键字与调用链耦合

8.1 基本语法存在的问题

但刚刚的基本语法存在一个问题:调用链耦合了其他模块的名称链。

看回例子: 模块 A 再调用模块 B 时,调用链耦合了 B 到 D 这个名称链 在实际项目中,模块 B 可能是其他公司开发的开源库 它写的名称链可能是 B → E → D 模块 A 的程序员是不可能要求 B 程序员按 A 的要求改名的

所以这种基本语法仅适用于模块内部,程序员可以完全控制代码的情况下。

8.2 API 关键字解决方案

当模块间进行交互时需要另外一种封装机制API 关键字。

`python class ModuleA: @API # 标记为公开接口 def public_interface(self): ...

def privatemethod(self): ...  # 内部方法

`

本语法中不存在其他面向对象语言中的 public 和 private 关键字,而是使用 API 关键字控制可见性。

8.3 API 关键字的作用

当模块 M 的未标记为 API 时: 该 M 的成员变量和成员函数全是 public 公有 可被外部任意访问

使用 API 关键字可以: 精确控制哪些接口对外暴露 将调用链耦合封装在模块内部 允许外部替换同接口的模块而不影响内部实现

九、总结

9.1 编程范式演进路径

阶段 特点 问题
过程式 简单直接 代码膨胀后难以维护
面向对象 class 语法便于封装 继承导致代码无法复用、重名冲突
MVC 组合思想 抽象泄露、初始化逻辑耦合
依赖注入/微服务 解决抽象泄露 太重型、成本高

9.2 新范式的核心要点

全路径巡境:通过名称链完整表达模块间的空间位置关系 编译期依赖注入:在编译阶段完成依赖关系的绑定 职能隔离:仿照工业流水线,每层程序员无需理解底层 模块可替换:衡量封装好不好的标准是能否自由替换同类模块 API 关键字:控制模块间交互的接口暴露

9.3 对面向对象的态度

本语法是对面向对象的语法的发展或改进,而非否定。它解决了: 继承的代码复用问题 多重继承的重名冲突问题 MVC 的抽象泄露问题 时间悖论导致的初始化耦合问题

相关讨论补充(来自弹幕): 边界条件处理和输入检查在任何范式下都需要考虑 用函数做 API 和用类做 API 区别挺大——类可以维护状态,函数式更纯粹 有些成员变量要维护的话,面向对象还是最合适的

────────────────────────────── Generated by MilkyAi@Bilibili: https://space.bilibili.com/3461574540921489