软件设计基础:抽象、模块化与接口
一、抽象:保留本质、隐藏细节
抽象(Abstraction)是在思考或表达时,只关注当前层次关心的东西,把其余当作「黑盒」忽略掉。用一句话说:保留本质、隐藏细节。例如「存储」:调用方只关心「存进去、取出来」,不关心是存在文件、数据库还是内存;「发送通知」只关心「发给谁、发什么」,不关心走邮件还是短信还是推送。抽象层次选对了,读代码、改代码都能「一次只面对一层复杂度」。
抽象不是「少写代码」,而是划清层次:上一层只依赖下一层的「能做什么」(接口),不依赖「怎么做」(实现)。这样换实现(例如存储从文件换成数据库)时,只要新实现满足同一接口,上层不用改。抽象不足会「什么都掺在一起」;过度抽象会「为了抽象而抽象、增加理解成本」——要按实际变化点来选抽象层次。
二、模块化与高内聚低耦合
模块化是把系统拆成一块一块(模块),每块有相对清晰的职责边界。高内聚:模块内部「为一类事服务」、相关逻辑聚在一起。低耦合:模块之间依赖少、接口小、改一块尽量不牵动其它块。高内聚低耦合是衡量「拆得好不好」的经典标准。
- 模块内职责混杂,改一处要动很多行
- 模块间互相直接调实现、传大量参数
- 改 A 可能影响 B、C、D,难以定位与测试
- 模块内「一类事」聚在一起,职责单一
- 模块间通过少量、稳定的接口协作
- 改一块影响范围清晰,易测、易维护
实践上:按职责或变化原因划分模块(例如「订单」「支付」「通知」分开);模块间只通过公开接口通信,不跨模块直接碰内部数据结构;依赖方向一致(例如业务层依赖数据层,而不是反过来),避免循环依赖。这样后续加功能、修 bug、换实现,都能控制在少数模块内。
三、接口与实现分离
接口是对外暴露的「能做什么」的约定(函数签名、API、协议);实现是「具体怎么做」的代码。分离的意思是:调用方依赖接口而不是实现。这样换实现(例如从 MySQL 换成 PostgreSQL)、加新实现(例如多一种存储方式)、做测试时用 Mock 实现,都不用改调用方。
在代码里:用接口类型(或抽象类、协议)声明依赖,注入具体实现(构造注入、参数传入等);不要在上层代码里 new 具体实现类,否则就绑死了。这样单元测试可以注入假实现,生产环境注入真实实现,扩展新实现也不影响已有调用方。
四、设计如何影响后续开发与维护成本
设计不是「一次性画完图就完事」,它直接决定后续每次改动的成本。设计得好:改需求时影响范围小、易定位、易测、敢重构;设计得差:改一处牵一发动全身、不敢动、只能打补丁,时间越久成本越高。
一句话: 抽象是「保留本质、隐藏细节」,让每一层只面对当前层次的复杂度。模块化要高内聚低耦合:块内职责清晰、块间通过少量接口协作。接口与实现分离让调用方依赖约定而非具体实现,便于替换、测试与扩展。好的设计会持续降低后续开发与维护成本;差的设计会让每次改动越来越贵。
五、小结
抽象是保留本质、隐藏细节,划清层次、让上层只依赖「能做什么」。模块化追求高内聚(块内一致)、低耦合(块间简单),按职责或变化原因划分、依赖方向一致。接口与实现分离让调用方依赖接口,实现可替换、可 Mock、可扩展。设计质量直接决定后续开发、测试、定位和重构的成本;设计好则长期成本可控,设计差则越改越贵。下一章我们讲面向对象分析与设计(OOAD)入门:对象、类与封装,职责划分与单一职责,继承、组合与多态。