内聚(Cohesion)和耦合(Coupling)是衡量模块独立性的两个核心指标。总体设计的核心目标就是追求高内聚、低耦合。
下面介绍各种内聚和耦合的概念,并结合新闻管理系统举例说明。
一、内聚(模块内部元素的紧密程度)
1. 偶然内聚(Coincidental Cohesion)——最差
概念:模块内的各部分之间没有任何关系,只是"碰巧"被放在一起。
新闻管理系统场景:
假设项目中有一个 CommonUtils.java 工具类:
public class CommonUtils {
public static int countWords(String text) { ... } // 统计新闻字数
public static void sendEmail(String to, String msg) { ... } // 发送邮件
public static String formatDate(Date date) { ... } // 格式化日期
public static double calculateTax(double money) { ... } // 计算税费
}
问题分析:
这4个方法毫无关系,只是"懒得新建文件"就塞进一个类。
修改
sendEmail可能误改到formatDate。无法复用:别人想用
formatDate,却被迫依赖整个CommonUtils。
2. 逻辑内聚(Logical Cohesion)
概念:模块内各部分逻辑相似,但功能不同,靠参数决定执行哪个。
新闻管理系统场景:
public class DataInput {
public void input(String type, Object data) {
if (type.equals("news")) {
// 录入新闻:标题、正文、分类
} else if (type.equals("user")) {
// 录入用户:用户名、密码、角色
} else if (type.equals("comment")) {
// 录入评论:新闻ID、内容
}
}
}
问题分析:
三种录入逻辑完全不同,却硬塞进一个方法。
调用者必须知道
type的取值,接口不清晰。修改新闻录入逻辑,可能影响用户录入。
3. 时间内聚(Temporal Cohesion)
概念:模块内各部分因为"在同一时间执行"而被组合在一起。
新闻管理系统场景:
public class SystemInit {
public void init() {
loadConfig(); // 加载配置文件
connectDatabase(); // 连接数据库
initRedisCache(); // 初始化Redis缓存
startLogService(); // 启动日志服务
registerScheduler(); // 注册定时任务
}
}
问题分析:
这些方法都在系统启动时执行,所以放在一起。
但它们功能彼此独立:数据库连接和日志服务毫无关系。
比偶然内聚好一点(至少有"启动"这个时间点),但功能上仍松散。
4. 过程内聚(Procedural Cohesion)
概念:模块内各部分必须按特定顺序执行。
新闻管理系统场景:
public class NewsPublishFlow {
public void publish(int newsId) {
checkNewsExists(newsId); // 1. 校验新闻是否存在
checkAuditStatus(newsId); // 2. 校验是否已审核通过
updateStatus(newsId); // 3. 更新状态为"已发布"
sendNotify(newsId); // 4. 通知作者
}
}
问题分析:
四个步骤有严格的先后顺序,缺一不可。
但它们操作的数据和功能各不相同(校验、更新、通知)。
比时间内聚好,因为顺序是业务决定的,不是"碰巧同时"。
5. 通信内聚(Communicational Cohesion)
概念:模块内各部分操作同一数据结构或输入输出同一数据。
新闻管理系统场景:
public class NewsDataHandler {
public void process(News news) {
validateNews(news); // 校验新闻字段
saveNews(news); // 保存新闻
printNews(news); // 打印新闻
logNews(news); // 记录日志
}
}
问题分析:
四个方法都围绕同一个
News对象操作,所以叫"通信内聚"。比过程内聚更紧密:因为它们共享同一份数据。
但每个方法的功能仍不同,独立性还不够高。
6. 顺序内聚(Sequential Cohesion)
概念:模块内各部分的输出是下一部分的输入,形成数据流。
新闻管理系统场景:
public class NewsAuditProcessor {
public AuditReport process(String rawContent) {
List<String> keywords = extractKeywords(rawContent); // 输出关键词
String category = classifyByKeywords(keywords); // 输入关键词,输出分类
AuditReport report = generateReport(category); // 输入分类,输出报告
return report;
}
}
问题分析:
extractKeywords的输出是classifyByKeywords的输入。classifyByKeywords的输出是generateReport的输入。数据流紧密相连,比通信内聚更内聚。
但仍不是最高,因为模块内仍包含三个不同功能。
7. 功能内聚(Functional Cohesion)——最好
概念:模块内所有元素共同完成一个单一、明确的功能,缺一不可。
新闻管理系统场景:
public class NewsTitleValidator {
public ValidationResult validate(String title) {
if (title == null || title.trim().isEmpty()) {
return ValidationResult.fail("标题不能为空");
}
if (title.length() > 100) {
return ValidationResult.fail("标题不能超过100字");
}
if (containsIllegalChars(title)) {
return ValidationResult.fail("标题含非法字符");
}
return ValidationResult.success();
}
}
问题分析:
这个模块只做一件事:校验新闻标题。
所有代码都为这一个功能服务,缺一不可。
高内聚,易于理解、测试、复用、维护。
这是设计的理想目标。
内聚排序:
偶然 < 逻辑 < 时间 < 过程 < 通信 < 顺序 < 功能
二、耦合(模块之间的紧密程度)
1. 内容耦合(Content Coupling)——最差
概念:一个模块直接访问另一个模块的内部数据或代码。
新闻管理系统场景:
public class NewsEditor {
public void edit(int newsId) {
// 直接访问 NewsRepository 的私有成员
NewsRepository repo = new NewsRepository();
repo.newsCache.put(newsId, "修改后的内容"); // 直接操作内部缓存
}
}
问题分析:
NewsEditor直接操作NewsRepository的私有newsCache。一旦
NewsRepository修改缓存结构,NewsEditor就会崩溃。这是最危险的耦合,必须严格避免。
2. 公共耦合(Common Coupling)
概念:多个模块共同访问同一个全局数据区。
新闻管理系统场景:
public class GlobalData {
public static List<News> newsList = new ArrayList<>();
public static Map<Integer, User> userMap = new HashMap<>();
}
public class NewsAddModule {
public void add(News news) {
GlobalData.newsList.add(news); // 直接操作全局列表
}
}
public class NewsDeleteModule {
public void delete(int id) {
GlobalData.newsList.removeIf(n -> n.getId() == id);
}
}
public class NewsQueryModule {
public List<News> query() {
return GlobalData.newsList; // 直接读取全局列表
}
}
问题分析:
三个模块都依赖
GlobalData.newsList。如果某天把
List改成Map,三个模块全要改。一处数据被污染,所有模块都受影响。
3. 外部耦合(External Coupling)
概念:模块间通过外部环境(文件格式、通信协议)耦合。
新闻管理系统场景:
public class NewsImporter {
public List<News> importFromXml(String path) {
// 依赖固定XML格式:<news><title>...</title><content>...</content></news>
}
}
public class NewsExporter {
public void exportToXml(List<News> news, String path) {
// 输出同样的XML格式
}
}
问题分析:
导入和导出模块都依赖同一个外部XML格式规范。
如果XML格式升级(比如加
<author>字段),两个模块都要改。这种耦合由外部标准决定,难以避免,但应尽量封装。
4. 控制耦合(Control Coupling)
概念:一个模块通过传递控制信号(如标志位)来控制另一个模块的执行逻辑。
新闻管理系统场景:
public class NewsService {
public void handle(News news, int flag) {
if (flag == 1) {
// 添加新闻
} else if (flag == 2) {
// 修改新闻
} else if (flag == 3) {
// 删除新闻
}
}
}
// 调用方
newsService.handle(news, 1); // 调用者必须知道 1 代表"添加"
问题分析:
调用者必须知道
flag=1/2/3的含义,等于了解了被调用者的内部逻辑。如果新增
flag=4,调用方可能也要改。应改为三个独立方法:
addNews、updateNews、deleteNews。
5. 标记耦合(Stamp Coupling)
概念:模块间传递复杂数据结构,但只使用其中一部分数据。
新闻管理系统场景:
public class NewsAuditService {
public void audit(News news) {
// 只用到 news.getId() 和 news.getStatus()
if (news.getStatus() == 0) {
updateStatus(news.getId(), 1);
}
}
}
问题分析:
audit方法接收整个News对象,但只用了id和status。如果
News类结构改变,audit方法可能受影响(尽管它不关心其他字段)。应改为传递简单参数:
audit(int newsId, int status)。
6. 数据耦合(Data Coupling)——较好
概念:模块间仅通过传递简单数据参数通信。
新闻管理系统场景:
public class NewsPublishService {
public void publish(int newsId, Date publishTime) {
// 只接收两个简单参数
newsRepository.updateStatus(newsId, 2);
newsRepository.updatePublishTime(newsId, publishTime);
}
}
问题分析:
publish只接收int和Date两个简单参数。不依赖任何复杂对象或全局数据。
依赖简单,易于测试、维护、复用。
这是设计中追求的目标之一。
7. 非直接耦合(No Direct Coupling)——最好
概念:模块之间没有直接联系,通过主模块协调。
新闻管理系统场景
public class NewsSystemMain {
public static void main(String[] args) {
NewsBrowseModule browse = new NewsBrowseModule();
UserManageModule user = new UserManageModule();
// 主模块协调,两个模块之间没有任何直接调用
user.login("admin", "123456");
browse.showNewsList();
}
}
问题分析:
NewsBrowseModule和UserManageModule之间没有任何直接联系。它们都由
NewsSystemMain主模块协调。独立性最高,但实际中较难完全做到(毕竟系统需要协作)。
这是耦合的理想状态。
耦合排序(从高到低,越靠前越差):
内容 > 公共 > 外部 > 控制 > 标记 > 数据 > 非直接
三、完整对比表
类型 内聚(越高越好) 新闻管理系统例子 最差 偶然内聚 CommonUtils 混放无关方法↓ 逻辑内聚 DataInput.input(type) 靠参数分支↓ 时间内聚 SystemInit.init() 启动时集中执行↓ 过程内聚 NewsPublishFlow.publish() 按顺序执行↓ 通信内聚 NewsDataHandler.process(news) 操作同一对象↓ 顺序内聚 NewsAuditProcessor 数据流串联最好 功能内聚 NewsTitleValidator.validate() 只做标题校验类型 耦合(越低越好) 新闻管理系统例子 最差 内容耦合 NewsEditor 直接改 NewsRepository 私有缓存↓ 公共耦合 三个模块共用 GlobalData.newsList↓ 外部耦合 导入导出依赖同一XML格式 ↓ 控制耦合 handle(news, flag) 靠标志位分支↓ 标记耦合 audit(News news) 只用了 id 和 status↓ 数据耦合 publish(int newsId, Date time) 传简单参数最好 非直接耦合 浏览模块和用户模块由主模块协调
四、一句话记忆口诀
内聚:从"只做一件事"到"凑在一起":
功顺通过时逻偶(功能>顺序>通信>过程>时间>逻辑>偶然)
耦合:从"互不打扰"到"直接伸手":
非数标控外公内(非直接<数据<标记<控制<外部<公共<内容)
设计目标:功能内聚 + 数据耦合
