06-模块-组织与复用
模块:组织与复用
在「系统→子系统→组件→模块→组装」的链条里,模块站在组织和复用的位置:组件实现职责(一个功能单元),模块把这些组件组织成一个完整功能,并让它可以被复用。这一条线讲两件事——模块怎么组织(数据+算法+接口的组合),模块怎么被复用(库、静态库、模板是复用的三种形态,各有边界)。「模块负责组织和复用」是拆解与组织主题下的一条独立线。
一、模块的双重身份
拆解与组织的链条里,每一层各司其职:
1 | 系统 ← 完整程序 |
模块与组件的区别:
1 | 组件:数据 + 算法 + 接口(功能单元,实现职责) |
所以模块站在链条的中间:向下组织组件,向上支撑复用。
二、组织:模块 = 数据 + 算法 + 接口
模块的第一重身份是「组织」——把实现一个功能的三个部分组合起来:
1 | 模块(一个功能) |
1 | process_list 模块(进程列表功能): |
组织的价值:模块内部三个部分各司其职,模块对外只暴露接口。调用者只知道 list_processes(),不知道它内部用了什么数据结构、什么算法。
模块的组织 = 对内组合数据/算法/接口,对外只留一个接口。 这是模块可复用的前提——复用者只依赖接口,不依赖内部。
三、复用:模块为什么能复用
模块的第二重身份是「复用」。复用的价值:同一份实现,被多个调用者使用,而不是每个调用者各写一份。
1 | 不复用:三个程序各写一遍「枚举进程」 |
复用成立的条件——模块对外只有接口,内部实现可替换:
1 | 调用者 → list_processes() ← 只依赖接口 |
模块为什么能复用:因为它把「怎么实现」藏在接口后面。 复用者看到的是接口,不是实现。
四、复用的三种形态:库、静态库、模板
模块被复用的方式有三种,粒度不同:
形态 1:库(模块的工程化交付)
模块在同一个工程里被复用,是「目录里多几个调用者」;跨工程复用,就需要做成库:
1 | 库 = 模块(或一组模块)的独立交付形式 |
业务系统做成库、网络系统做成库、渲染层做成库——都是「这个模块值得被多个工程复用」的标志。
形态 2:静态库 / 动态库(复用的两种链接方式)
库又分两种,区别在链接时机:
1 | 静态库(.a / .lib):链接进可执行文件 |
静态库是发布期物理边界,动态库是运行期物理边界(见边界线)。
形态 3:模板(类型维度的复用)
库复用的是「同一份实现」,模板复用的是「同一份逻辑、不同类型的实现」:
1 | 库: 一份 list_processes(),被多个调用者复用(数据固定) |
模板是复用的最细粒度——它复用的不是整个模块,而是模块里的算法部分。
五、复用的边界:什么时候值得做成库
不是所有模块都值得复用。判断标准:
按「是否值得独立演化、复用、替换」来决定,而不是把所有模块都做成库。
1 | 值得复用:被多个调用者使用、变化独立、需要独立测试 |
过早做库的成本:
1 | ❌ 只有一个调用者就做成库 |
复用的边界 = 真实存在多个调用者。 一个调用者时是模块,第二个调用者出现时才是库。
六、复用带来接口稳定
模块一旦被复用,它的接口就被「冻结」了:
1 | 模块被一个调用者使用:接口可以随便改(改完同步改调用者) |
所以复用和接口先行是同一件事的两面:
1 | 复用者:只依赖接口,不依赖实现 |
七、工程规模与拆分粒度
模块怎么拆、拆到多细,取决于复杂度而不是项目大小:
| 规模 | 主要组织单位 | 结构 |
|---|---|---|
| 小型 | 文件 | 一个程序 → 多个职责文件 |
| 中型 | 组件/文件夹 | 一个系统 → 多个功能组件 |
| 大型 | 子系统 + 组件 | 系统 → 子系统 → 组件 |
1 | 小型:src/ main.cpp parser.cpp analyzer.cpp formatter.cpp output.cpp |
7.1 组件化先按功能拆,再在内部按技术拆
传统「模块化」先按技术职责切(service/data/algorithm/utils),容易把一个完整功能拆散到多个目录里;「组件化」先按功能/职责边界拆,再在组件内部按数据/算法/接口拆实现。
| 传统模块化 | 组件化 | |
|---|---|---|
| 切分维度 | 技术类别 | 功能/职责边界 |
| 回答的问题 | 代码按什么技术类别摆放 | 这个功能作为独立单元需要什么 |
| 典型问题 | 一个功能被拆散到多个目录 | 功能+实现+接口+依赖形成完整单元 |
结论不是「模块化过时」,而是:组件化可以看作模块化进一步发展的结果;组件本身就是一种更强的模块。 模块是组织代码的手段,组件是组织功能的手段。
1 | 需求 → 功能分析 → 子系统划分 → 组件划分 |
7.2 两条独立的复杂度轴
1 | 规模维度(横向):文件 → 组件 → 子系统 |
「要拆分数据结构与算法,必然会形成中型工程」——因为这不是单纯「把代码分文件」,而是在建立组件内部的工程结构。组件内部结构不要求统一——简单组件只有 .h/.cpp,复杂组件才展开 data/ algorithm/ api/。
7.3 完整的职责拆分粒度阶梯
1 | 系统 → 子系统 → 组件 → 文件 → 类 → 函数 |
各粒度承担的「职责」不同:
| 粒度 | 职责 |
|---|---|
| 函数 | 最小职责单元:一个明确操作 |
| 类 | 对象/状态/行为的封装 |
| 文件 | 代码物理组织和编译单元边界 |
| 组件 | 独立功能单元 |
| 子系统 | 一组相关功能 |
| 系统 | 完整程序 |
按复杂度逐级建立职责边界:
1 | 极小:函数 |
软件不是一开始就「模块化」,而是根据复杂度,在不同粒度建立职责边界。 模块/组件只是这个阶梯上承担「组织与复用」的那一级。
八、和其他线的关系
1 | 模块线 ←→ 拆解与组织原则 |
收束
1 | 模块的双重身份: |
模块 = 组织(向下组合组件)+ 复用(向上支撑多个调用者)。 组织让模块可用,复用让模块值得存在。
