链接:
时间内聚是指模块中的各个功能由于需要在同一时间段内执行而被组合在一起。这种内聚形式通常出现在初始化、清理或定时任务的场景中,其中多个操作可能没有直接的逻辑关系,但它们需要在特定的时间点或时间段内完成。
尽管时间内聚比偶然内聚(Coincidental Cohesion)要好一些,但它仍然不是理想的内聚形式。这是因为模块内部的功能虽然有一定的执行时间上的联系,但这些功能并不紧密相关,通常只是因为它们需要在同一时间段内完成而被组合在一起。
时间内聚的特点:
- 基于时间:模块中的功能需要在同一时间段内执行,例如程序启动时的初始化操作或关闭时的清理操作。
- 部分相关性:模块中的功能可能具有一定的执行顺序要求,但它们之间并没有直接的逻辑依赖。
- 较低的内聚度:由于模块内的功能并不是完全相关的,导致其内聚度相对较低。
时间内聚的问题
- 低可读性:由于模块内部的功能并不是完全相关的,代码的可读性和可理解性较差,开发人员需要仔细阅读和理解模块中每个操作的目的。
- 高维护成本:当需要修改某个功能时,由于模块内部的功能不完全相关,开发人员可能需要检查整个模块以确保不会引入不必要的副作用。
- 脆弱性:由于模块内部的功能不完全相关,任何对模块的修改都可能导致其他不相关的部分出现问题,增加了系统的脆弱性。
- 难以重用:由于模块内部的功能不完全相关,很难将模块中的某个功能单独提取出来进行重用。
举例说明时间内聚
假设我们正在开发一个应用程序,其中有一个模块 SystemInitializer 被设计用来处理系统启动时的各种初始化操作,例如加载配置文件、初始化数据库连接和启动日志记录器。这个模块的所有功能都需要在系统启动时完成。
public class SystemInitializer
{
public void initializeSystem()
{
loadConfiguration();
initializeDatabaseConnection();
startLoggingService();
}
private void loadConfiguration()
{
// 假设这里有一些加载配置文件的逻辑
System.out.println("Loading configuration...");
}
private void initializeDatabaseConnection()
{
// 假设这里有一些初始化数据库连接的逻辑
System.out.println("Initializing database connection...");
}
private void startLoggingService()
{
// 假设这里有一些启动日志记录器的逻辑
System.out.println("Starting logging service...");
}
}
在这个例子中,SystemInitializer 类包含了一个 initializeSystem 方法,该方法依次调用了三个不同的初始化操作。尽管这些操作都需要在系统启动时完成,但它们之间并没有直接的逻辑依赖关系。
这就是典型的时间内聚,因为模块内部的功能是基于它们需要在同一时间段内执行而组合在一起的。
改进方案:提高模块的内聚性
为了提高模块的内聚性,可以采用以下几种策略:
- 分离功能:将不相关的功能拆分到不同的模块中,使每个模块专注于特定的任务。
- 封装相关功能:将相关的功能封装在一起,形成高内聚的模块。
- 使用事件驱动模型:利用事件驱动的设计模式,通过事件机制来触发不同的初始化操作。
改进后的示例代码:
我们可以将上述不相关的初始化操作拆分成独立的模块,并利用接口和事件驱动模型来实现不同的初始化操作。
// 定义初始化操作接口
public interface InitializationTask {
void execute();
}
// 加载配置文件操作实现类
public class LoadConfigurationTask implements InitializationTask {
@Override
public void execute() {
// 假设这里有一些加载配置文件的逻辑
System.out.println("Loading configuration...");
}
}
// 初始化数据库连接操作实现类
public class InitializeDatabaseTask implements InitializationTask {
@Override
public void execute() {
// 假设这里有一些初始化数据库连接的逻辑
System.out.println("Initializing database connection...");
}
}
// 启动日志记录器操作实现类
public class StartLoggingTask implements InitializationTask {
@Override
public void execute() {
// 假设这里有一些启动日志记录器的逻辑
System.out.println("Starting logging service...");
}
}
// 系统初始化器类,负责根据任务列表执行初始化操作
public class SystemInitializer {
private final List<InitializationTask> tasks;
public SystemInitializer(List<InitializationTask> tasks) {
this.tasks = tasks;
}
public void initializeSystem() {
for (InitializationTask task : tasks) {
task.execute();
}
}
}
改进后的分析:
分离功能:
- 将不相关的初始化操作(加载配置文件、初始化数据库连接、启动日志记录器)拆分到不同的类中,每个类只负责一个明确的任务。这符合单一职责原则,使得每个类更加专注和清晰。
封装相关功能:
- 使用接口 InitializationTask 来封装所有初始化操作的共同行为(即 execute 方法),并通过具体的实现类(如 LoadConfigurationTask、InitializeDatabaseTask 和 StartLoggingTask)来实现不同的初始化操作。这种方式提高了模块的内聚性,使得每个类的功能更加紧密相关。
提高可读性和可维护性:
- 通过将不相关的功能拆分到不同的类中,代码的可读性和可维护性得到了显著提升。开发人员可以更容易地理解和修改每个类的功能。
降低脆弱性:
- 由于每个类只负责一个明确的任务,修改其中一个类不会影响到其他类,降低了系统的脆弱性。
便于重用:
- 现在每个类都可以独立使用,便于在其他地方重用。例如,LoadConfigurationTask 可以在其他需要加载配置文件的地方直接使用,而不需要依赖于其他不相关的功能。